diff options
author | Pierre Schmitz <pierre@archlinux.de> | 2009-06-10 13:00:47 +0200 |
---|---|---|
committer | Pierre Schmitz <pierre@archlinux.de> | 2009-06-10 13:00:47 +0200 |
commit | 72e90545454c0e014318fa3c81658e035aac58c1 (patch) | |
tree | 9212e3f46868989c4d57ae9a5c8a1a80e4dc0702 /includes | |
parent | 565a0ccc371ec1a2a0e9b39487cbac18e6f60e25 (diff) |
applying patch to version 1.15.0
Diffstat (limited to 'includes')
231 files changed, 35363 insertions, 17570 deletions
diff --git a/includes/AjaxResponse.php b/includes/AjaxResponse.php index 63468a14..26b6f443 100644 --- a/includes/AjaxResponse.php +++ b/includes/AjaxResponse.php @@ -45,7 +45,7 @@ class AjaxResponse { $this->mText = ''; $this->mResponseCode = '200 OK'; $this->mLastModified = false; - $this->mContentType= 'text/html; charset=utf-8'; + $this->mContentType= 'application/x-wiki'; if ( $text ) { $this->addText( $text ); @@ -178,6 +178,7 @@ class AjaxResponse { wfDebug( "$fname: -- client send If-Modified-Since: " . $modsince . "\n", false ); wfDebug( "$fname: -- we might send Last-Modified : $lastmod\n", false ); if( ($ismodsince >= $timestamp ) && $wgUser->validateCache( $ismodsince ) && $ismodsince >= $wgCacheEpoch ) { + ini_set('zlib.output_compression', 0); $this->setResponseCode( "304 Not Modified" ); $this->disable(); $this->mLastModified = $lastmod; diff --git a/includes/Article.php b/includes/Article.php index 3d9c2147..ef219ea3 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -84,12 +84,12 @@ class Article { return $this->mRedirectTarget; # Query the redirect table $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'redirect', + $row = $dbr->selectRow( 'redirect', array('rd_namespace', 'rd_title'), - array('rd_from' => $this->getID()), + array('rd_from' => $this->getID() ), __METHOD__ ); - if( $row = $dbr->fetchObject($res) ) { + if( $row ) { return $this->mRedirectTarget = Title::makeTitle($row->rd_namespace, $row->rd_title); } # This page doesn't have an entry in the redirect table @@ -135,7 +135,7 @@ class Article { * @return mixed false, Title of in-wiki target, or string with URL */ public function followRedirectText( $text ) { - $rt = Title::newFromRedirect( $text ); + $rt = Title::newFromRedirectRecurse( $text ); // recurse through to only get the final target # process if title object is valid and not special:userlogout if( $rt ) { if( $rt->getInterwiki() != '' ) { @@ -218,7 +218,7 @@ class Article { if( wfEmptyMsg( $message, $text ) ) $text = ''; } else { - $text = wfMsg( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon' ); + $text = wfMsgExt( $wgUser->isLoggedIn() ? 'noarticletext' : 'noarticletextanon', 'parsemag' ); } wfProfileOut( __METHOD__ ); return $text; @@ -228,6 +228,21 @@ class Article { return $this->mContent; } } + + /** + * Get the text of the current revision. No side-effects... + * + * @return Return the text of the current revision + */ + public function getRawText() { + // Check process cache for current revision + if( $this->mContentLoaded && $this->mOldId == 0 ) { + return $this->mContent; + } + $rev = Revision::newFromTitle( $this->mTitle ); + $text = $rev ? $rev->getRawText() : false; + return $text; + } /** * This function returns the text of a section, specified by a number ($section). @@ -245,6 +260,28 @@ class Article { global $wgParser; return $wgParser->getSection( $text, $section ); } + + /** + * Get the text that needs to be saved in order to undo all revisions + * between $undo and $undoafter. Revisions must belong to the same page, + * must exist and must not be deleted + * @param $undo Revision + * @param $undoafter Revision Must be an earlier revision than $undo + * @return mixed string on success, false on failure + */ + public function getUndoText( Revision $undo, Revision $undoafter = null ) { + $undo_text = $undo->getText(); + $undoafter_text = $undoafter->getText(); + $cur_text = $this->getContent(); + if ( $cur_text == $undo_text ) { + # No use doing a merge if it's just a straight revert. + return $undoafter_text; + } + $undone_text = ''; + if ( !wfMerge( $undo_text, $undoafter_text, $cur_text, $undone_text ) ) + return false; + return $undone_text; + } /** * @return int The oldid of the article that is to be shown, 0 for the @@ -569,7 +606,7 @@ class Article { } // Apparently loadPageData was never called $this->loadContent(); - $titleObj = Title::newFromRedirect( $this->fetchContent() ); + $titleObj = Title::newFromRedirectRecurse( $this->fetchContent() ); } else { $titleObj = Title::newFromRedirect( $text ); } @@ -660,10 +697,13 @@ class Article { $user = $this->getUser(); $pageId = $this->getId(); + $hideBit = Revision::DELETED_USER; // username hidden? + $sql = "SELECT {$userTable}.*, MAX(rev_timestamp) as timestamp FROM $revTable LEFT JOIN $userTable ON rev_user = user_id WHERE rev_page = $pageId AND rev_user != $user + AND rev_deleted & $hideBit = 0 GROUP BY rev_user, rev_user_text, user_real_name ORDER BY timestamp DESC"; @@ -687,21 +727,28 @@ class Article { global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies; global $wgDefaultRobotPolicy; + # Let the parser know if this is the printable version + if( $wgOut->isPrintable() ) { + $wgOut->parserOptions()->setIsPrintable( true ); + } + wfProfileIn( __METHOD__ ); # Get variables from query string $oldid = $this->getOldID(); - # Try file cache + # Try client and file cache if( $oldid === 0 && $this->checkTouched() ) { global $wgUseETag; if( $wgUseETag ) { $parserCache = ParserCache::singleton(); - $wgOut->setETag( $parserCache->getETag($this,$wgUser) ); + $wgOut->setETag( $parserCache->getETag($this, $wgOut->parserOptions()) ); } + # Is is client cached? if( $wgOut->checkLastModified( $this->getTouched() ) ) { wfProfileOut( __METHOD__ ); return; + # Try file cache } else if( $this->tryFileCache() ) { # tell wgOut that output is taken care of $wgOut->disable(); @@ -743,15 +790,17 @@ class Article { } $wgOut->setRobotPolicy( $policy ); + # Allow admins to see deleted content if explicitly requested + $delId = $diff ? $diff : $oldid; + $unhide = $wgRequest->getInt('unhide') == 1 + && $wgUser->matchEditToken( $wgRequest->getVal('token'), $delId ); # If we got diff and oldid in the query, we want to see a # diff page instead of the article. - if( !is_null( $diff ) ) { $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - $diff = $wgRequest->getVal( 'diff' ); $htmldiff = $wgRequest->getVal( 'htmldiff' , false); - $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff); + $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge, $htmldiff, $unhide ); // DifferenceEngine directly fetched the revision: $this->mRevIdFetched = $de->mNewid; $de->showDiffPage( $diffOnly ); @@ -765,6 +814,16 @@ class Article { wfProfileOut( __METHOD__ ); return; } + + if( $ns == NS_USER || $ns == NS_USER_TALK ) { + # User/User_talk subpages are not modified. (bug 11443) + if( !$this->mTitle->isSubpage() ) { + $block = new Block(); + if( $block->load( $this->mTitle->getBaseText() ) ) { + $wgOut->setRobotpolicy( 'noindex,nofollow' ); + } + } + } # Should the parser cache be used? $pcache = $this->useParserCache( $oldid ); @@ -787,6 +846,11 @@ class Article { $fragment = Xml::escapeJsString( $this->mTitle->getFragmentForURL() ); $wgOut->addInlineScript( "redirectToFragment(\"$fragment\");" ); } + + // Add a <link rel="canonical"> tag + $wgOut->addLink( array( 'rel' => 'canonical', + 'href' => $this->mTitle->getLocalURL() ) + ); $wasRedirected = true; } } elseif( !empty( $rdfrom ) ) { @@ -803,7 +867,7 @@ class Article { $outputDone = false; wfRunHooks( 'ArticleViewHeader', array( &$this, &$outputDone, &$pcache ) ); - if( $pcache && $wgOut->tryParserCache( $this, $wgUser ) ) { + if( $pcache && $wgOut->tryParserCache( $this ) ) { // Ensure that UI elements requiring revision ID have // the correct version information. $wgOut->setRevisionId( $this->mLatest ); @@ -816,14 +880,18 @@ class Article { $this->showDeletionLog(); } $text = $this->getContent(); - if( $text === false ) { + // For now, check also for ID until getContent actually returns + // false for pages that do not exists + if( $text === false || $this->getID() === 0 ) { # Failed to load, replace text with error message $t = $this->mTitle->getPrefixedText(); if( $oldid ) { - $d = wfMsgExt( 'missingarticle-rev', array( 'escape' ), $oldid ); - $text = wfMsg( 'missing-article', $t, $d ); - } else { - $text = wfMsg( 'noarticletext' ); + $d = wfMsgExt( 'missingarticle-rev', 'escape', $oldid ); + $text = wfMsgExt( 'missing-article', 'parsemag', $t, $d ); + // Always use page content for pages in the MediaWiki namespace + // since it contains the default message + } elseif ( $this->mTitle->getNamespace() != NS_MEDIAWIKI ) { + $text = wfMsgExt( 'noarticletext', 'parsemag' ); } } @@ -836,7 +904,7 @@ class Article { // for better machine handling of broken links. $return404 = true; } - } + } if( $return404 ) { $wgRequest->response()->header( "HTTP/1.x 404 Not Found" ); @@ -862,24 +930,35 @@ class Article { // FIXME: This would be a nice place to load the 'no such page' text. } else { $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid ); + # Allow admins to see deleted content if explicitly requested if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { - if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); + if( !$unhide || !$this->mRevision->userCan(Revision::DELETED_TEXT) ) { + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); wfProfileOut( __METHOD__ ); return; } else { - $wgOut->addWikiMsg( 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); // and we are allowed to see... } } + // Is this the current revision and otherwise cacheable? Try the parser cache... + if( $oldid === $this->getLatest() && $this->useParserCache( false ) + && $wgOut->tryParserCache( $this ) ) + { + $outputDone = true; + } } } + // Ensure that UI elements requiring revision ID have + // the correct version information. $wgOut->setRevisionId( $this->getRevIdFetched() ); - // Pages containing custom CSS or JavaScript get special treatment - if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { + if( $outputDone ) { + // do nothing... + // Pages containing custom CSS or JavaScript get special treatment + } else if( $this->mTitle->isCssOrJsPage() || $this->mTitle->isCssJsSubpage() ) { $wgOut->addHTML( wfMsgExt( 'clearyourcache', 'parse' ) ); // Give hooks a chance to customise the output if( wfRunHooks( 'ShowRawCssJs', array( $this->mContent, $this->mTitle, $wgOut ) ) ) { @@ -890,7 +969,7 @@ class Article { $wgOut->addHTML( htmlspecialchars( $this->mContent ) ); $wgOut->addHTML( "\n</pre>\n" ); } - } else if( $rt = Title::newFromRedirect( $text ) ) { + } else if( $rt = Title::newFromRedirectArray( $text ) ) { # get an array of redirect targets # Don't append the subtitle if this was an old revision $wgOut->addHTML( $this->viewRedirect( $rt, !$wasRedirected && $this->isCurrent() ) ); $parseout = $wgParser->parse($text, $this->mTitle, ParserOptions::newFromUser($wgUser)); @@ -942,7 +1021,7 @@ class Article { # If we have been passed an &rcid= parameter, we want to give the user a # chance to mark this new article as patrolled. - if( !empty($rcid) && $this->mTitle->exists() && $this->mTitle->userCan('patrol') ) { + if( !empty($rcid) && $this->mTitle->exists() && $this->mTitle->quickUserCan('patrol') ) { $wgOut->addHTML( "<div class='patrollink'>" . wfMsgHtml( 'markaspatrolledlink', @@ -1003,24 +1082,41 @@ class Article { /** * View redirect - * @param $target Title object of destination to redirect + * @param $target Title object or Array of destination(s) to redirect * @param $appendSubtitle Boolean [optional] * @param $forceKnown Boolean: should the image be shown as a bluelink regardless of existence? */ public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) { global $wgParser, $wgOut, $wgContLang, $wgStylePath, $wgUser; # Display redirect + if( !is_array( $target ) ) { + $target = array( $target ); + } $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; - $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; - + $imageUrl = $wgStylePath . '/common/images/redirect' . $imageDir . '.png'; + $imageUrl2 = $wgStylePath . '/common/images/nextredirect' . $imageDir . '.png'; + $alt2 = $wgContLang->isRTL() ? '←' : '→'; // should -> and <- be used instead of entities? + if( $appendSubtitle ) { $wgOut->appendSubtitle( wfMsgHtml( 'redirectpagesub' ) ); } $sk = $wgUser->getSkin(); + // the loop prepends the arrow image before the link, so the first case needs to be outside + $title = array_shift( $target ); if( $forceKnown ) { - $link = $sk->makeKnownLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + $link = $sk->makeKnownLinkObj( $title, htmlspecialchars( $title->getFullText() ) ); } else { - $link = $sk->makeLinkObj( $target, htmlspecialchars( $target->getFullText() ) ); + $link = $sk->makeLinkObj( $title, htmlspecialchars( $title->getFullText() ) ); + } + // automatically append redirect=no to each link, since most of them are redirect pages themselves + foreach( $target as $rt ) { + if( $forceKnown ) { + $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />' + . $sk->makeKnownLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) ); + } else { + $link .= '<img src="'.$imageUrl2.'" alt="'.$alt2.' " />' + . $sk->makeLinkObj( $rt, htmlspecialchars( $rt->getFullText() ) ); + } } return '<img src="'.$imageUrl.'" alt="#REDIRECT " />' . '<span class="redirectText">'.$link.'</span>'; @@ -1052,7 +1148,7 @@ class Article { $o->tb_name, $rmvtxt); } - $wgOut->addWikiMsg( 'trackbackbox', $tbtext ); + $wgOut->wrapWikiMsg( "<div id='mw_trackbacks'>$1</div>\n", array( 'trackbackbox', $tbtext ) ); $this->mTitle->invalidateCache(); } @@ -1128,7 +1224,7 @@ class Article { if( $this->getID() == 0 ) { $text = false; } else { - $text = $this->getContent(); + $text = $this->getRawText(); } $wgMessageCache->replace( $this->mTitle->getDBkey(), $text ); } @@ -1490,7 +1586,7 @@ class Article { $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed('minoredit'); $bot = $flags & EDIT_FORCE_BOT; - $oldtext = $this->getContent(); + $oldtext = $this->getRawText(); // current revision $oldsize = strlen( $oldtext ); # Provide autosummaries if one is not provided and autosummaries are enabled. @@ -1601,8 +1697,8 @@ class Article { } # Invalidate cache of this article and all pages using this article - # as a template. Partly deferred. Leave templatelinks for editUpdates(). - Article::onArticleEdit( $this->mTitle, 'skiptransclusions' ); + # as a template. Partly deferred. + Article::onArticleEdit( $this->mTitle ); # Update links tables, site stats, etc. $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed ); } else { @@ -1680,7 +1776,7 @@ class Article { $status->value['revision'] = $revision; wfRunHooks( 'ArticleSaveComplete', array( &$this, &$user, $text, $summary, - $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status ) ); + $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId ) ); wfProfileOut( __METHOD__ ); return $status; @@ -1731,7 +1827,7 @@ class Article { #It would be nice to see where the user had actually come from, but for now just guess $returnto = $rc->getAttribute( 'rc_type' ) == RC_NEW ? 'Newpages' : 'Recentchanges'; - $return = Title::makeTitle( NS_SPECIAL, $returnto ); + $return = SpecialPage::getTitleFor( $returnto ); $dbw = wfGetDB( DB_MASTER ); $errors = $rc->doMarkPatrolled(); @@ -1867,7 +1963,18 @@ class Article { global $wgUser, $wgRestrictionTypes, $wgContLang; $id = $this->mTitle->getArticleID(); - if( $id <= 0 || wfReadOnly() || !$this->mTitle->userCan('protect') ) { + if ( $id <= 0 ) { + wfDebug( "updateRestrictions failed: $id <= 0\n" ); + return false; + } + + if ( wfReadOnly() ) { + wfDebug( "updateRestrictions failed: read-only\n" ); + return false; + } + + if ( !$this->mTitle->userCan( 'protect' ) ) { + wfDebug( "updateRestrictions failed: insufficient permissions\n" ); return false; } @@ -1938,6 +2045,9 @@ class Article { $encodedExpiry = array(); $protect_description = ''; foreach( $limit as $action => $restrictions ) { + if ( !isset($expiry[$action]) ) + $expiry[$action] = 'infinite'; + $encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw ); if( $restrictions != '' ) { $protect_description .= "[$action=$restrictions] ("; @@ -2099,7 +2209,7 @@ class Article { // Calculate the maximum amount of chars to get // Max content length = max comment length - length of the comment (excl. $1) - '...' $maxLength = 255 - (strlen( $reason ) - 2) - 3; - $contents = $wgContLang->truncate( $contents, $maxLength, '...' ); + $contents = $wgContLang->truncate( $contents, $maxLength ); // Remove possible unfinished links $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents ); // Now replace the '$1' placeholder @@ -2124,7 +2234,7 @@ class Article { if( $reason != 'other' && $this->DeleteReason != '' ) { // Entry from drop down menu + additional comment - $reason .= ': ' . $this->DeleteReason; + $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason; } elseif( $reason == 'other' ) { $reason = $this->DeleteReason; } @@ -2398,7 +2508,7 @@ class Article { return false; } - $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 ); + $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getRawText() ), -1 ); array_push( $wgDeferredUpdateList, $u ); // Bitfields to further suppress the content @@ -2500,7 +2610,6 @@ class Article { # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID( 0 ); - $this->mTitle->mArticleID = 0; # Log the deletion, if the page was suppressed, log it at Oversight instead $logtype = $suppress ? 'suppress' : 'delete'; @@ -2812,13 +2921,15 @@ class Article { # Save it to the parser cache if( $wgEnableParserCache ) { + $popts = new ParserOptions; + $popts->setTidy( true ); + $popts->enableLimitReport(); $parserCache = ParserCache::singleton(); - $parserCache->save( $editInfo->output, $this, $wgUser ); + $parserCache->save( $editInfo->output, $this, $popts ); } # Update the links tables - $u = new LinksUpdate( $this->mTitle, $editInfo->output, false ); - $u->setRecursiveTouch( $changed ); // refresh/invalidate including pages too + $u = new LinksUpdate( $this->mTitle, $editInfo->output ); $u->doUpdate(); wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $changed ) ); @@ -2903,7 +3014,7 @@ class Article { * @param $oldid String: revision ID of this article revision */ public function setOldSubtitle( $oldid = 0 ) { - global $wgLang, $wgOut, $wgUser; + global $wgLang, $wgOut, $wgUser, $wgRequest; if( !wfRunHooks( 'DisplayOldSubtitle', array( &$this, &$oldid ) ) ) { return; @@ -2954,16 +3065,17 @@ class Article { } $cdel = "(<small>$cdel</small>) "; } - # Show user links if allowed to see them. Normally they - # are hidden regardless, but since we can already see the text here... - $userlinks = $sk->revUserTools( $revision, false ); + $unhide = $wgRequest->getInt('unhide') == 1 && $wgUser->matchEditToken( $wgRequest->getVal('token'), $oldid ); + # Show user links if allowed to see them. If hidden, then show them only if requested... + $userlinks = $sk->revUserTools( $revision, !$unhide ); $m = wfMsg( 'revision-info-current' ); $infomsg = $current && !wfEmptyMsg( 'revision-info-current', $m ) && $m != '-' ? 'revision-info-current' : 'revision-info'; - $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt( $infomsg, array( 'parseinline', 'replaceafter' ), $td, $userlinks, $revision->getID() ) . "</div>\n" . + $r = "\n\t\t\t\t<div id=\"mw-{$infomsg}\">" . wfMsgExt( $infomsg, array( 'parseinline', 'replaceafter' ), + $td, $userlinks, $revision->getID() ) . "</div>\n" . "\n\t\t\t\t<div id=\"mw-revision-nav\">" . $cdel . wfMsgExt( 'revision-nav', array( 'escapenoentities', 'parsemag', 'replaceafter' ), $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff ) . "</div>\n\t\t\t"; @@ -3056,7 +3168,7 @@ class Article { if( !$this->mDataLoaded ) { $this->loadPageData(); } - return $this->mLatest; + return (int)$this->mLatest; } /** @@ -3206,17 +3318,18 @@ class Article { $user = User::newFromName( $title->getText(), false ); $user->setNewtalk( false ); } + # Image redirects + RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title ); } /** * Purge caches on page update etc */ - public static function onArticleEdit( $title, $transclusions = 'transclusions' ) { + public static function onArticleEdit( $title, $flags = '' ) { global $wgDeferredUpdateList; // Invalidate caches of articles which include this page - if( $transclusions !== 'skiptransclusions' ) - $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' ); + $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'templatelinks' ); // Invalidate the caches of all pages which redirect here $wgDeferredUpdateList[] = new HTMLCacheUpdate( $title, 'redirect' ); @@ -3405,8 +3518,7 @@ class Article { global $wgContLang; $truncatedtext = $wgContLang->truncate( str_replace("\n", ' ', $newtext), - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ), - '...' ); + max( 0, 200 - strlen( wfMsgForContent( 'autosumm-new' ) ) ) ); return wfMsgForContent( 'autosumm-new', $truncatedtext ); } @@ -3418,9 +3530,7 @@ class Article { global $wgContLang; $truncatedtext = $wgContLang->truncate( $newtext, - max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ), - '...' - ); + max( 0, 200 - strlen( wfMsgForContent( 'autosumm-replace' ) ) ) ); return wfMsgForContent( 'autosumm-replace', $truncatedtext ); } @@ -3449,7 +3559,7 @@ class Article { $popts->enableLimitReport( false ); if( $wgEnableParserCache && $cache && $this && $parserOutput->getCacheTime() != -1 ) { $parserCache = ParserCache::singleton(); - $parserCache->save( $parserOutput, $this, $wgUser ); + $parserCache->save( $parserOutput, $this, $popts ); } // Make sure file cache is not used on uncacheable content. // Output that has magic words in it can still use the parser cache @@ -3478,27 +3588,26 @@ class Article { __METHOD__ ); global $wgContLang; - - if( $res !== false ) { - foreach( $res as $row ) { - $tlTemplates[] = $wgContLang->getNsText( $row->tl_namespace ) . ':' . $row->tl_title ; - } + foreach( $res as $row ) { + $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true; } # Get templates from parser output. - $poTemplates_allns = $parserOutput->getTemplates(); - - $poTemplates = array (); - foreach ( $poTemplates_allns as $ns_templates ) { - $poTemplates = array_merge( $poTemplates, $ns_templates ); + $poTemplates = array(); + foreach ( $parserOutput->getTemplates() as $ns => $templates ) { + foreach ( $templates as $dbk => $id ) { + $key = $row->tl_namespace . ':'. $row->tl_title; + $poTemplates["$ns:$dbk"] = true; + } } # Get the diff - $templates_diff = array_diff( $poTemplates, $tlTemplates ); + # Note that we simulate array_diff_key in PHP <5.0.x + $templates_diff = array_diff_key( $poTemplates, $tlTemplates ); if( count( $templates_diff ) > 0 ) { # Whee, link updates time. - $u = new LinksUpdate( $this->mTitle, $parserOutput ); + $u = new LinksUpdate( $this->mTitle, $parserOutput, false ); $u->doUpdate(); } } diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index ce1912ea..85e7e668 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -19,6 +19,7 @@ $wgAutoloadLocalClasses = array( 'AuthPlugin' => 'includes/AuthPlugin.php', 'AuthPluginUser' => 'includes/AuthPlugin.php', 'Autopromote' => 'includes/Autopromote.php', + 'BacklinkCache' => 'includes/BacklinkCache.php', 'BagOStuff' => 'includes/BagOStuff.php', 'Block' => 'includes/Block.php', 'CacheDependency' => 'includes/CacheDependency.php', @@ -28,6 +29,7 @@ $wgAutoloadLocalClasses = array( 'CategoryViewer' => 'includes/CategoryPage.php', 'ChangesList' => 'includes/ChangesList.php', 'ChangesFeed' => 'includes/ChangesFeed.php', + 'ChangeTags' => 'includes/ChangeTags.php', 'ChannelFeed' => 'includes/Feed.php', 'ConcatenatedGzipHistoryBlob' => 'includes/HistoryBlob.php', 'ConstantDependency' => 'includes/CacheDependency.php', @@ -73,6 +75,7 @@ $wgAutoloadLocalClasses = array( 'FileDependency' => 'includes/CacheDependency.php', 'FileRevertForm' => 'includes/FileRevertForm.php', 'FileStore' => 'includes/FileStore.php', + 'ForkController' => 'includes/ForkController.php', 'FormatExif' => 'includes/Exif.php', 'FormOptions' => 'includes/FormOptions.php', 'FSException' => 'includes/FileStore.php', @@ -186,6 +189,7 @@ $wgAutoloadLocalClasses = array( 'StringUtils' => 'includes/StringUtils.php', 'TablePager' => 'includes/Pager.php', 'ThumbnailImage' => 'includes/MediaTransformOutput.php', + 'TiffHandler' => 'includes/media/Tiff.php', 'TitleDependency' => 'includes/CacheDependency.php', 'Title' => 'includes/Title.php', 'TitleArray' => 'includes/TitleArray.php', @@ -193,10 +197,6 @@ $wgAutoloadLocalClasses = array( 'TransformParameterError' => 'includes/MediaTransformOutput.php', 'TurckBagOStuff' => 'includes/BagOStuff.php', 'UnlistedSpecialPage' => 'includes/SpecialPage.php', - 'UploadBase' => 'includes/UploadBase.php', - 'UploadFromStash' => 'includes/UploadFromStash.php', - 'UploadFromUpload' => 'includes/UploadFromUpload.php', - 'UploadFromUrl' => 'includes/UploadFromUrl.php', 'User' => 'includes/User.php', 'UserArray' => 'includes/UserArray.php', 'UserArrayFromResult' => 'includes/UserArray.php', @@ -231,11 +231,14 @@ $wgAutoloadLocalClasses = array( 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', 'ApiFormatJson' => 'includes/api/ApiFormatJson.php', 'ApiFormatPhp' => 'includes/api/ApiFormatPhp.php', + 'ApiFormatRaw' => 'includes/api/ApiFormatRaw.php', 'ApiFormatTxt' => 'includes/api/ApiFormatTxt.php', 'ApiFormatWddx' => 'includes/api/ApiFormatWddx.php', 'ApiFormatXml' => 'includes/api/ApiFormatXml.php', 'ApiFormatYaml' => 'includes/api/ApiFormatYaml.php', 'ApiHelp' => 'includes/api/ApiHelp.php', + 'ApiImport' => 'includes/api/ApiImport.php', + 'ApiImportReporter' => 'includes/api/ApiImport.php', 'ApiLogin' => 'includes/api/ApiLogin.php', 'ApiLogout' => 'includes/api/ApiLogout.php', 'ApiMain' => 'includes/api/ApiMain.php', @@ -273,6 +276,7 @@ $wgAutoloadLocalClasses = array( 'ApiQueryLangLinks' => 'includes/api/ApiQueryLangLinks.php', 'ApiQueryLinks' => 'includes/api/ApiQueryLinks.php', 'ApiQueryLogEvents' => 'includes/api/ApiQueryLogEvents.php', + 'ApiQueryProtectedTitles' => 'includes/api/ApiQueryProtectedTitles.php', 'ApiQueryRandom' => 'includes/api/ApiQueryRandom.php', 'ApiQueryRecentChanges'=> 'includes/api/ApiQueryRecentChanges.php', 'ApiQueryRevisions' => 'includes/api/ApiQueryRevisions.php', @@ -320,6 +324,11 @@ $wgAutoloadLocalClasses = array( 'PostgresField' => 'includes/db/DatabasePostgres.php', 'ResultWrapper' => 'includes/db/Database.php', 'SQLiteField' => 'includes/db/DatabaseSqlite.php', + + 'DatabaseIbm_db2' => 'includes/db/DatabaseIbm_db2.php', + 'IBM_DB2Field' => 'includes/db/DatabaseIbm_db2.php', + 'IBM_DB2SearchResultSet' => 'includes/SearchIBM_DB2.php', + 'SearchIBM_DB2' => 'includes/SearchIBM_DB2.php', # includes/diff 'AncestorComparator' => 'includes/diff/HTMLDiff.php', @@ -431,6 +440,7 @@ $wgAutoloadLocalClasses = array( 'Preprocessor_DOM' => 'includes/parser/Preprocessor_DOM.php', 'Preprocessor_Hash' => 'includes/parser/Preprocessor_Hash.php', 'StripState' => 'includes/parser/Parser.php', + 'MWTidy' => 'includes/parser/Tidy.php', # includes/specials 'AncientPagesPage' => 'includes/specials/SpecialAncientpages.php', @@ -474,11 +484,12 @@ $wgAutoloadLocalClasses = array( 'PopularPagesPage' => 'includes/specials/SpecialPopularpages.php', 'PreferencesForm' => 'includes/specials/SpecialPreferences.php', 'RandomPage' => 'includes/specials/SpecialRandompage.php', - 'RevisionDeleteForm' => 'includes/specials/SpecialRevisiondelete.php', + 'SpecialRevisionDelete' => 'includes/specials/SpecialRevisiondelete.php', 'RevisionDeleter' => 'includes/specials/SpecialRevisiondelete.php', 'ShortPagesPage' => 'includes/specials/SpecialShortpages.php', 'SpecialAllpages' => 'includes/specials/SpecialAllpages.php', 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', + 'SpecialExport' => 'includes/specials/SpecialExport.php', 'SpecialImport' => 'includes/specials/SpecialImport.php', 'SpecialListGroupRights' => 'includes/specials/SpecialListgrouprights.php', 'SpecialMostlinkedtemplates' => 'includes/specials/SpecialMostlinkedtemplates.php', @@ -489,6 +500,7 @@ $wgAutoloadLocalClasses = array( 'SpecialSearch' => 'includes/specials/SpecialSearch.php', 'SpecialSearchOld' => 'includes/specials/SpecialSearch.php', 'SpecialStatistics' => 'includes/specials/SpecialStatistics.php', + 'SpecialTags' => 'includes/specials/SpecialTags.php', 'SpecialVersion' => 'includes/specials/SpecialVersion.php', 'UncategorizedCategoriesPage' => 'includes/specials/SpecialUncategorizedcategories.php', 'UncategorizedPagesPage' => 'includes/specials/SpecialUncategorizedpages.php', @@ -556,7 +568,7 @@ class AutoLoader { } if ( !$filename ) { if( function_exists( 'wfDebug' ) ) - wfDebug( "Class {$className} not found; skipped loading" ); + wfDebug( "Class {$className} not found; skipped loading\n" ); # Give up return false; } @@ -575,7 +587,7 @@ class AutoLoader { global $wgAutoloadClasses; foreach( $wgAutoloadClasses as $class => $file ) { - if( !( class_exists( $class ) || interface_exists( $class ) ) ) { + if( !( class_exists( $class, false ) || interface_exists( $class, false ) ) ) { require( $file ); } } diff --git a/includes/BacklinkCache.php b/includes/BacklinkCache.php new file mode 100644 index 00000000..a7bcd858 --- /dev/null +++ b/includes/BacklinkCache.php @@ -0,0 +1,232 @@ +<?php + +/** + * Class for fetching backlink lists, approximate backlink counts and partitions. + * Instances of this class should typically be fetched with $title->getBacklinkCache(). + * + * Ideally you should only get your backlinks from here when you think there is some + * advantage in caching them. Otherwise it's just a waste of memory. + */ +class BacklinkCache { + var $partitionCache = array(); + var $fullResultCache = array(); + var $title; + var $db; + + const CACHE_EXPIRY = 3600; + + /** + * Create a new BacklinkCache + */ + function __construct( $title ) { + $this->title = $title; + } + + /** + * Clear locally stored data + */ + function clear() { + $this->partitionCache = array(); + $this->fullResultCache = array(); + unset( $this->db ); + } + + /** + * Set the Database object to use + */ + public function setDB( $db ) { + $this->db = $db; + } + + protected function getDB() { + if ( !isset( $this->db ) ) { + $this->db = wfGetDB( DB_SLAVE ); + } + return $this->db; + } + + /** + * Get the backlinks for a given table. Cached in process memory only. + * @param string $table + * @return TitleArray + */ + public function getLinks( $table, $startId = false, $endId = false ) { + wfProfileIn( __METHOD__ ); + + if ( $startId || $endId ) { + // Partial range, not cached + wfDebug( __METHOD__.": from DB (uncacheable range)\n" ); + $conds = $this->getConditions( $table ); + // Use the from field in the condition rather than the joined page_id, + // because databases are stupid and don't necessarily propagate indexes. + $fromField = $this->getPrefix( $table ) . '_from'; + if ( $startId ) { + $conds[] = "$fromField >= " . intval( $startId ); + } + if ( $endId ) { + $conds[] = "$fromField <= " . intval( $endId ); + } + $res = $this->getDB()->select( + array( $table, 'page' ), + array( 'page_namespace', 'page_title', 'page_id'), + $conds, + __METHOD__, + array('STRAIGHT_JOIN') ); + $ta = TitleArray::newFromResult( $res ); + wfProfileOut( __METHOD__ ); + return $ta; + } + + if ( !isset( $this->fullResultCache[$table] ) ) { + wfDebug( __METHOD__.": from DB\n" ); + $res = $this->getDB()->select( + array( $table, 'page' ), + array( 'page_namespace', 'page_title', 'page_id' ), + $this->getConditions( $table ), + __METHOD__, + array('STRAIGHT_JOIN') ); + $this->fullResultCache[$table] = $res; + } + $ta = TitleArray::newFromResult( $this->fullResultCache[$table] ); + wfProfileOut( __METHOD__ ); + return $ta; + } + + /** + * Get the field name prefix for a given table + */ + protected function getPrefix( $table ) { + static $prefixes = array( + 'pagelinks' => 'pl', + 'imagelinks' => 'il', + 'categorylinks' => 'cl', + 'templatelinks' => 'tl', + 'redirect' => 'rd', + ); + if ( isset( $prefixes[$table] ) ) { + return $prefixes[$table]; + } else { + throw new MWException( "Invalid table \"$table\" in " . __CLASS__ ); + } + } + + /** + * Get the SQL condition array for selecting backlinks, with a join on the page table + */ + protected function getConditions( $table ) { + $prefix = $this->getPrefix( $table ); + switch ( $table ) { + case 'pagelinks': + case 'templatelinks': + case 'redirect': + $conds = array( + "{$prefix}_namespace" => $this->title->getNamespace(), + "{$prefix}_title" => $this->title->getDBkey(), + "page_id={$prefix}_from" + ); + break; + case 'imagelinks': + $conds = array( + 'il_to' => $this->title->getDBkey(), + 'page_id=il_from' + ); + break; + case 'categorylinks': + $conds = array( + 'cl_to' => $this->title->getDBkey(), + 'page_id=cl_from', + ); + break; + default: + throw new MWException( "Invalid table \"$table\" in " . __CLASS__ ); + } + return $conds; + } + + /** + * Get the approximate number of backlinks + */ + public function getNumLinks( $table ) { + if ( isset( $this->fullResultCache[$table] ) ) { + return $this->fullResultCache[$table]->numRows(); + } + if ( isset( $this->partitionCache[$table] ) ) { + $entry = reset( $this->partitionCache[$table] ); + return $entry['numRows']; + } + $titleArray = $this->getLinks( $table ); + return $titleArray->count(); + } + + /** + * Partition the backlinks into batches. + * Returns an array giving the start and end of each range. The first batch has + * a start of false, and the last batch has an end of false. + * + * @param string $table The links table name + * @param integer $batchSize + * @return array + */ + public function partition( $table, $batchSize ) { + // Try cache + if ( isset( $this->partitionCache[$table][$batchSize] ) ) { + wfDebug( __METHOD__.": got from partition cache\n" ); + return $this->partitionCache[$table][$batchSize]['batches']; + } + $this->partitionCache[$table][$batchSize] = false; + $cacheEntry =& $this->partitionCache[$table][$batchSize]; + + // Try full result cache + if ( isset( $this->fullResultCache[$table] ) ) { + $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize ); + wfDebug( __METHOD__.": got from full result cache\n" ); + return $cacheEntry['batches']; + } + // Try memcached + global $wgMemc; + $memcKey = wfMemcKey( 'backlinks', md5( $this->title->getPrefixedDBkey() ), + $table, $batchSize ); + $memcValue = $wgMemc->get( $memcKey ); + if ( is_array( $memcValue ) ) { + $cacheEntry = $memcValue; + wfDebug( __METHOD__.": got from memcached $memcKey\n" ); + return $cacheEntry['batches']; + } + // Fetch from database + $this->getLinks( $table ); + $cacheEntry = $this->partitionResult( $this->fullResultCache[$table], $batchSize ); + // Save to memcached + $wgMemc->set( $memcKey, $cacheEntry, self::CACHE_EXPIRY ); + wfDebug( __METHOD__.": got from database\n" ); + return $cacheEntry['batches']; + } + + /** + * Partition a DB result with backlinks in it into batches + */ + protected function partitionResult( $res, $batchSize ) { + $batches = array(); + $numRows = $res->numRows(); + $numBatches = ceil( $numRows / $batchSize ); + for ( $i = 0; $i < $numBatches; $i++ ) { + if ( $i == 0 ) { + $start = false; + } else { + $rowNum = intval( $numRows * $i / $numBatches ); + $res->seek( $rowNum ); + $row = $res->fetchObject(); + $start = $row->page_id; + } + if ( $i == $numBatches - 1 ) { + $end = false; + } else { + $rowNum = intval( $numRows * ( $i + 1 ) / $numBatches ); + $res->seek( $rowNum ); + $row = $res->fetchObject(); + $end = $row->page_id - 1; + } + $batches[] = array( $start, $end ); + } + return array( 'numRows' => $numRows, 'batches' => $batches ); + } +} diff --git a/includes/Block.php b/includes/Block.php index 2c2227e2..a44941f1 100644 --- a/includes/Block.php +++ b/includes/Block.php @@ -105,6 +105,7 @@ class Block { && $this->mHideName == $block->mHideName && $this->mBlockEmail == $block->mBlockEmail && $this->mAllowUsertalk == $block->mAllowUsertalk + && $this->mReason == $block->mReason ); } @@ -570,7 +571,7 @@ class Block { ## Allow hooks to cancel the autoblock. if (!wfRunHooks( 'AbortAutoblock', array( $autoblockIP, &$this ) )) { - wfDebug( "Autoblock aborted by hook." ); + wfDebug( "Autoblock aborted by hook.\n" ); return false; } diff --git a/includes/Category.php b/includes/Category.php index 78567add..50efdbc1 100644 --- a/includes/Category.php +++ b/includes/Category.php @@ -41,8 +41,7 @@ class Category { $dbr = wfGetDB( DB_SLAVE ); $row = $dbr->selectRow( 'category', - array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', - 'cat_files' ), + array( 'cat_id', 'cat_title', 'cat_pages', 'cat_subcats', 'cat_files' ), $where, __METHOD__ ); @@ -70,8 +69,7 @@ class Category { # (bug 13683) If the count is negative, then 1) it's obviously wrong # and should not be kept, and 2) we *probably* don't have to scan many # rows to obtain the correct figure, so let's risk a one-time recount. - if( $this->mPages < 0 || $this->mSubcats < 0 || - $this->mFiles < 0 ) { + if( $this->mPages < 0 || $this->mSubcats < 0 || $this->mFiles < 0 ) { $this->refreshCounts(); } diff --git a/includes/CategoryPage.php b/includes/CategoryPage.php index 4ac24b5f..03ecb5dc 100644 --- a/includes/CategoryPage.php +++ b/includes/CategoryPage.php @@ -20,7 +20,8 @@ class CategoryPage extends Article { if ( isset( $diff ) && $diffOnly ) return Article::view(); - if(!wfRunHooks('CategoryPageView', array(&$this))) return; + if( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) + return; if ( NS_CATEGORY == $this->mTitle->getNamespace() ) { $this->openShowCategory(); @@ -28,10 +29,6 @@ class CategoryPage extends Article { Article::view(); - # If the article we've just shown is in the "Image" namespace, - # follow it with the history list and link list for the image - # it describes. - if ( NS_CATEGORY == $this->mTitle->getNamespace() ) { $this->closeShowCategory(); } @@ -79,7 +76,7 @@ class CategoryViewer { $this->from = $from; $this->until = $until; $this->limit = $wgCategoryPagingLimit; - $this->cat = Category::newFromName( $title->getDBKey() ); + $this->cat = Category::newFromTitle( $title ); } /** @@ -192,9 +189,10 @@ class CategoryViewer { */ function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) { global $wgContLang; + $titletext = $wgContLang->convert( $title->getPrefixedText() ); $this->articles[] = $isRedirect - ? '<span class="redirect-in-category">' . $this->getSkin()->makeKnownLinkObj( $title ) . '</span>' - : $this->getSkin()->makeSizeLinkObj( $pageLength, $title ); + ? '<span class="redirect-in-category">' . $this->getSkin()->makeKnownLinkObj( $title, $titletext ) . '</span>' + : $this->getSkin()->makeSizeLinkObj( $pageLength, $title, $titletext ); $this->articles_start_char[] = $wgContLang->convert( $wgContLang->firstChar( $sortkey ) ); } @@ -208,7 +206,7 @@ class CategoryViewer { } function doCategoryQuery() { - $dbr = wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_SLAVE, 'category' ); if( $this->from != '' ) { $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from ); $this->flip = false; @@ -316,7 +314,7 @@ class CategoryViewer { $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' ); return "<div id=\"mw-category-media\">\n" . - '<h2>' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "</h2>\n" . + '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n" . $countmsg . $this->gallery->toHTML() . "\n</div>"; } else { return ''; @@ -451,12 +449,12 @@ class CategoryViewer { $sk = $this->getSkin(); $limitText = $wgLang->formatNum( $limit ); - $prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) ); + $prevLink = wfMsgExt( 'prevn', array( 'escape', 'parsemag' ), $limitText ); if( $first != '' ) { $prevLink = $sk->makeLinkObj( $title, $prevLink, wfArrayToCGI( $query + array( 'until' => $first ) ) ); } - $nextLink = htmlspecialchars( wfMsg( 'nextn', $limitText ) ); + $nextLink = wfMsgExt( 'nextn', array( 'escape', 'parsemag' ), $limitText ); if( $last != '' ) { $nextLink = $sk->makeLinkObj( $title, $nextLink, wfArrayToCGI( $query + array( 'from' => $last ) ) ); diff --git a/includes/Categoryfinder.php b/includes/Categoryfinder.php index 4413bd1a..7c1c2856 100644 --- a/includes/Categoryfinder.php +++ b/includes/Categoryfinder.php @@ -53,9 +53,11 @@ class Categoryfinder { # Set the list of target categories; convert them to DBKEY form first $this->targets = array () ; foreach ( $categories AS $c ) { - $ct = Title::newFromText ( $c , NS_CATEGORY ) ; - $c = $ct->getDBkey () ; - $this->targets[$c] = $c ; + $ct = Title::makeTitleSafe( NS_CATEGORY, $c ); + if( $ct ) { + $c = $ct->getDBkey(); + $this->targets[$c] = $c; + } } } diff --git a/includes/ChangeTags.php b/includes/ChangeTags.php new file mode 100644 index 00000000..de804c5c --- /dev/null +++ b/includes/ChangeTags.php @@ -0,0 +1,183 @@ +<?php + +if (!defined( 'MEDIAWIKI' )) + die; + +class ChangeTags { + static function formatSummaryRow( $tags, $page ) { + if (!$tags) + return array('',array()); + + $classes = array(); + + $tags = explode( ',', $tags ); + $displayTags = array(); + foreach( $tags as $tag ) { + $displayTags[] = self::tagDescription( $tag ); + $classes[] = "mw-tag-$tag"; + } + + return array( '(' . implode( ', ', $displayTags ) . ')', $classes ); + } + + static function tagDescription( $tag ) { + $msg = wfMsgExt( "tag-$tag", 'parseinline' ); + if ( wfEmptyMsg( "tag-$tag", $msg ) ) { + return htmlspecialchars($tag); + } + return $msg; + } + + ## Basic utility method to add tags to a particular change, given its rc_id, rev_id and/or log_id. + static function addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params = null ) { + if ( !is_array($tags) ) { + $tags = array( $tags ); + } + + $tags = array_filter( $tags ); // Make sure we're submitting all tags... + + if (!$rc_id && !$rev_id && !$log_id) { + throw new MWException( "At least one of: RCID, revision ID, and log ID MUST be specified when adding a tag to a change!" ); + } + + $dbr = wfGetDB( DB_SLAVE ); + + // Might as well look for rcids and so on. + if (!$rc_id) { + $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. + if ($log_id) { + $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_logid' => $log_id ), __METHOD__ ); + } elseif ($rev_id) { + $rc_id = $dbr->selectField( 'recentchanges', 'rc_id', array( 'rc_this_oldid' => $rev_id ), __METHOD__ ); + } + } elseif (!$log_id && !$rev_id) { + $dbr = wfGetDB( DB_MASTER ); // Info might be out of date, somewhat fractionally, on slave. + $log_id = $dbr->selectField( 'recentchanges', 'rc_logid', array( 'rc_id' => $rc_id ), __METHOD__ ); + $rev_id = $dbr->selectField( 'recentchanges', 'rc_this_oldid', array( 'rc_id' => $rc_id ), __METHOD__ ); + } + + $tsConds = array_filter( array( 'ts_rc_id' => $rc_id, 'ts_rev_id' => $rev_id, 'ts_log_id' => $log_id ) ); + + ## Update the summary row. + $prevTags = $dbr->selectField( 'tag_summary', 'ts_tags', $tsConds, __METHOD__ ); + $prevTags = $prevTags ? $prevTags : ''; + $prevTags = array_filter( explode( ',', $prevTags ) ); + $newTags = array_unique( array_merge( $prevTags, $tags ) ); + sort($prevTags); + sort($newTags); + + if ( $prevTags == $newTags ) { + // No change. + return false; + } + + $dbw = wfGetDB( DB_MASTER ); + $dbw->replace( 'tag_summary', array( 'ts_rev_id', 'ts_rc_id', 'ts_log_id' ), array_filter( array_merge( $tsConds, array( 'ts_tags' => implode( ',', $newTags ) ) ) ), __METHOD__ ); + + // Insert the tags rows. + $tagsRows = array(); + foreach( $tags as $tag ) { // Filter so we don't insert NULLs as zero accidentally. + $tagsRows[] = array_filter( array( 'ct_tag' => $tag, 'ct_rc_id' => $rc_id, 'ct_log_id' => $log_id, 'ct_rev_id' => $rev_id, 'ct_params' => $params ) ); + } + + $dbw->insert( 'change_tag', $tagsRows, __METHOD__, array('IGNORE') ); + + return true; + } + + /** + * Applies all tags-related changes to a query. + * Handles selecting tags, and filtering. + * Needs $tables to be set up properly, so we can figure out which join conditions to use. + */ + static function modifyDisplayQuery( &$tables, &$fields, &$conds, + &$join_conds, &$options, $filter_tag = false ) { + global $wgRequest, $wgUseTagFilter; + + if ($filter_tag === false) { + $filter_tag = $wgRequest->getVal( 'tagfilter' ); + } + + // Figure out which conditions can be done. + $join_field = ''; + if ( in_array('recentchanges', $tables) ) { + $join_cond = 'rc_id'; + } elseif( in_array('logging', $tables) ) { + $join_cond = 'log_id'; + } elseif ( in_array('revision', $tables) ) { + $join_cond = 'rev_id'; + } else { + throw new MWException( "Unable to determine appropriate JOIN condition for tagging." ); + } + + // JOIN on tag_summary + $tables[] = 'tag_summary'; + $join_conds['tag_summary'] = array( 'LEFT JOIN', "ts_$join_cond=$join_cond" ); + $fields[] = 'ts_tags'; + + if ($wgUseTagFilter && $filter_tag) { + // Somebody wants to filter on a tag. + // Add an INNER JOIN on change_tag + + // FORCE INDEX -- change_tags will almost ALWAYS be the correct query plan. + $options['USE INDEX'] = array( 'change_tag' => 'change_tag_tag_id' ); + unset( $options['FORCE INDEX'] ); + $tables[] = 'change_tag'; + $join_conds['change_tag'] = array( 'INNER JOIN', "ct_$join_cond=$join_cond" ); + $conds['ct_tag'] = $filter_tag; + } + } + + /** + * If $fullForm is set to false, then it returns an array of (label, form). + * If $fullForm is true, it returns an entire form. + */ + static function buildTagFilterSelector( $selected='', $fullForm = false /* used to put a full form around the selector */ ) { + global $wgUseTagFilter; + + if ( !$wgUseTagFilter || !count( self::listDefinedTags() ) ) + return $fullForm ? '' : array(); + + global $wgTitle; + + $data = array( wfMsgExt( 'tag-filter', 'parseinline' ), Xml::input( 'tagfilter', 20, $selected ) ); + + if (!$fullForm) { + return $data; + } + + $html = implode( ' ', $data ); + $html .= "\n" . Xml::element( 'input', array( 'type' => 'submit', 'value' => wfMsg( 'tag-filter-submit' ) ) ); + $html .= "\n" . Xml::hidden( 'title', $wgTitle-> getPrefixedText() ); + $html = Xml::tags( 'form', array( 'action' => $wgTitle->getLocalURL(), 'method' => 'get' ), $html ); + + return $html; + } + + /** Basically lists defined tags which count even if they aren't applied to anything */ + static function listDefinedTags() { + // Caching... + global $wgMemc; + $key = wfMemcKey( 'valid-tags' ); + + if ($tags = $wgMemc->get( $key )) + return $tags; + + $emptyTags = array(); + + // Some DB stuff + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'valid_tag', 'vt_tag', array(), __METHOD__ ); + while( $row = $res->fetchObject() ) { + $emptyTags[] = $row->vt_tag; + } + + wfRunHooks( 'ListDefinedTags', array(&$emptyTags) ); + + $emptyTags = array_filter( array_unique( $emptyTags ) ); + + // Short-term caching. + $wgMemc->set( $key, $emptyTags, 300 ); + return $emptyTags; + } +} diff --git a/includes/ChangesFeed.php b/includes/ChangesFeed.php index f3c3e429..a0c2767a 100644 --- a/includes/ChangesFeed.php +++ b/includes/ChangesFeed.php @@ -18,16 +18,16 @@ class ChangesFeed { $feedTitle, htmlspecialchars( $description ), $wgTitle->getFullUrl() ); } - public function execute( $feed, $rows, $limit = 0 , $hideminor = false, $lastmod = false ) { + public function execute( $feed, $rows, $limit=0, $hideminor=false, $lastmod=false, $target='' ) { global $messageMemc, $wgFeedCacheTimeout; - global $wgFeedClasses, $wgSitename, $wgContLanguageCode; + global $wgSitename, $wgContLanguageCode; if ( !FeedUtils::checkFeedOutput( $this->format ) ) { return; } $timekey = wfMemcKey( $this->type, $this->format, 'timestamp' ); - $key = wfMemcKey( $this->type, $this->format, 'limit', $limit, 'minor', $hideminor ); + $key = wfMemcKey( $this->type, $this->format, $limit, $hideminor, $target ); FeedUtils::checkPurge($timekey, $key); diff --git a/includes/ChangesList.php b/includes/ChangesList.php index a8f5fff0..4eda1dbd 100644 --- a/includes/ChangesList.php +++ b/includes/ChangesList.php @@ -108,7 +108,7 @@ class ChangesList { public static function showCharacterDifference( $old, $new ) { global $wgRCChangedSizeThreshold, $wgLang; $szdiff = $new - $old; - $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape'), $wgLang->formatNum($szdiff) ); + $formatedSize = wfMsgExt( 'rc-change-size', array( 'parsemag', 'escape' ), $wgLang->formatNum( $szdiff ) ); if( abs( $szdiff ) > abs( $wgRCChangedSizeThreshold ) ) { $tag = 'strong'; } else { @@ -223,9 +223,9 @@ class ChangesList { } /** Insert links to user page, user talk page and eventually a blocking link */ - public function insertUserRelatedLinks(&$s, &$rc) { - if( $this->isDeleted($rc,Revision::DELETED_USER) ) { - $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>'; + public function insertUserRelatedLinks( &$s, &$rc ) { + if( $this->isDeleted( $rc, Revision::DELETED_USER ) ) { + $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>'; } else { $s .= $this->skin->userLink( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); $s .= $this->skin->userToolLinks( $rc->mAttribs['rc_user'], $rc->mAttribs['rc_user_text'] ); @@ -233,22 +233,22 @@ class ChangesList { } /** insert a formatted action */ - protected function insertAction(&$s, &$rc) { + protected function insertAction( &$s, &$rc ) { if( $rc->mAttribs['rc_type'] == RC_LOG ) { - if( $this->isDeleted($rc,LogPage::DELETED_ACTION) ) { - $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>'; + if( $this->isDeleted( $rc, LogPage::DELETED_ACTION ) ) { + $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>'; } else { $s .= ' '.LogPage::actionText( $rc->mAttribs['rc_log_type'], $rc->mAttribs['rc_log_action'], - $rc->getTitle(), $this->skin, LogPage::extractParams($rc->mAttribs['rc_params']), true, true ); + $rc->getTitle(), $this->skin, LogPage::extractParams( $rc->mAttribs['rc_params'] ), true, true ); } } } /** insert a formatted comment */ - protected function insertComment(&$s, &$rc) { + protected function insertComment( &$s, &$rc ) { if( $rc->mAttribs['rc_type'] != RC_MOVE && $rc->mAttribs['rc_type'] != RC_MOVE_OVER_REDIRECT ) { - if( $this->isDeleted($rc,Revision::DELETED_COMMENT) ) { - $s .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>'; + if( $this->isDeleted( $rc, Revision::DELETED_COMMENT ) ) { + $s .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span>'; } else { $s .= $this->skin->commentBlock( $rc->mAttribs['rc_comment'], $rc->getTitle() ); } @@ -272,8 +272,8 @@ class ChangesList { static $cache = array(); if( $count > 0 ) { if( !isset( $cache[$count] ) ) { - $cache[$count] = wfMsgExt('number_of_watching_users_RCview', - array('parsemag', 'escape'), $wgLang->formatNum($count)); + $cache[$count] = wfMsgExt( 'number_of_watching_users_RCview', + array('parsemag', 'escape' ), $wgLang->formatNum( $count ) ); } return $cache[$count]; } else { @@ -288,7 +288,7 @@ class ChangesList { * @return bool */ public static function isDeleted( $rc, $field ) { - return ($rc->mAttribs['rc_deleted'] & $field) == $field; + return ( $rc->mAttribs['rc_deleted'] & $field ) == $field; } /** @@ -318,6 +318,40 @@ class ChangesList { return '<span class="mw-rc-unwatched">' . $link . '</span>'; } } + + /** Inserts a rollback link */ + protected function insertRollback( &$s, &$rc ) { + global $wgUser; + if( !$rc->mAttribs['rc_new'] && $rc->mAttribs['rc_this_oldid'] && $rc->mAttribs['rc_cur_id'] ) { + $page = $rc->getTitle(); + /** Check for rollback and edit permissions, disallow special pages, and only + * show a link on the top-most revision */ + if ($wgUser->isAllowed('rollback') && $rc->mAttribs['page_latest'] == $rc->mAttribs['rc_this_oldid'] ) + { + $rev = new Revision( array( + 'id' => $rc->mAttribs['rc_this_oldid'], + 'user' => $rc->mAttribs['rc_user'], + 'user_text' => $rc->mAttribs['rc_user_text'], + 'deleted' => $rc->mAttribs['rc_deleted'] + ) ); + $rev->setTitle( $page ); + $s .= ' '.$this->skin->generateRollback( $rev ); + } + } + } + + protected function insertTags( &$s, &$rc, &$classes ) { + if ( empty($rc->mAttribs['ts_tags']) ) + return; + + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $rc->mAttribs['ts_tags'], 'changeslist' ); + $classes = array_merge( $classes, $newClasses ); + $s .= ' ' . $tagSummary; + } + + protected function insertExtra( &$s, &$rc, &$classes ) { + ## Empty, used for subclassers to add anything special. + } } @@ -328,8 +362,8 @@ class OldChangesList extends ChangesList { /** * Format a line using the old system (aka without any javascript). */ - public function recentChangesLine( &$rc, $watched = false ) { - global $wgContLang, $wgRCShowChangedSize, $wgUser; + public function recentChangesLine( &$rc, $watched = false, $linenumber = NULL ) { + global $wgContLang, $wgLang, $wgRCShowChangedSize, $wgUser; wfProfileIn( __METHOD__ ); # Should patrol-related stuff be shown? $unpatrolled = $wgUser->useRCPatrol() && !$rc->mAttribs['rc_patrolled']; @@ -338,6 +372,17 @@ class OldChangesList extends ChangesList { $this->insertDateHeader( $dateheader, $rc->mAttribs['rc_timestamp'] ); $s = ''; + $classes = array(); + // use mw-line-even/mw-line-odd class only if linenumber is given (feature from bug 14468) + if( $linenumber ) { + if( $linenumber & 1 ) { + $classes[] = 'mw-line-odd'; + } + else { + $classes[] = 'mw-line-even'; + } + } + // Moved pages if( $rc->mAttribs['rc_type'] == RC_MOVE || $rc->mAttribs['rc_type'] == RC_MOVE_OVER_REDIRECT ) { $this->insertMove( $s, $rc ); @@ -369,25 +414,32 @@ class OldChangesList extends ChangesList { } } # User tool links - $this->insertUserRelatedLinks($s,$rc); + $this->insertUserRelatedLinks( $s, $rc ); # Log action text (if any) - $this->insertAction($s, $rc); + $this->insertAction( $s, $rc ); # Edit or log comment - $this->insertComment($s, $rc); + $this->insertComment( $s, $rc ); + # Tags + $this->insertTags( $s, $rc, $classes ); + # Rollback + $this->insertRollback( $s, $rc ); + # For subclasses + $this->insertExtra( $s, $rc, $classes ); + # Mark revision as deleted if so if( !$rc->mAttribs['rc_log_type'] && $this->isDeleted($rc,Revision::DELETED_TEXT) ) { $s .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>'; } # How many users watch this page if( $rc->numberofWatchingusers > 0 ) { - $s .= ' ' . wfMsg( 'number_of_watching_users_RCview', - $wgContLang->formatNum($rc->numberofWatchingusers) ); + $s .= ' ' . wfMsgExt( 'number_of_watching_users_RCview', + array( 'parsemag', 'escape' ), $wgLang->formatNum( $rc->numberofWatchingusers ) ); } wfRunHooks( 'OldChangesListRecentChangesLine', array(&$this, &$s, $rc) ); wfProfileOut( __METHOD__ ); - return "$dateheader<li>$s</li>\n"; + return "$dateheader<li class=\"".implode( ' ', $classes )."\">$s</li>\n"; } } @@ -417,6 +469,8 @@ class EnhancedChangesList extends ChangesList { */ public function recentChangesLine( &$baseRC, $watched = false ) { global $wgLang, $wgContLang, $wgUser; + + wfProfileIn( __METHOD__ ); # Create a specialised object $rc = RCCacheEntry::newFromParent( $baseRC ); @@ -508,10 +562,8 @@ class EnhancedChangesList extends ChangesList { if( !$showdifflinks ) { $curLink = $this->message['cur']; $diffLink = $this->message['diff']; - } else if( $rc_type == RC_NEW || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { - if( $rc_type != RC_NEW ) { - $curLink = $this->message['cur']; - } + } else if( in_array( $rc_type, array(RC_NEW,RC_LOG,RC_MOVE,RC_MOVE_OVER_REDIRECT) ) ) { + $curLink = ($rc_type != RC_NEW) ? $this->message['cur'] : $curLink; $diffLink = $this->message['diff']; } else { $diffLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['diff'], @@ -519,9 +571,9 @@ class EnhancedChangesList extends ChangesList { } # Make "last" link - if( !$showdifflinks ) { + if( !$showdifflinks || !$rc_last_oldid ) { $lastLink = $this->message['last']; - } else if( $rc_last_oldid == 0 || $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { + } else if( $rc_type == RC_LOG || $rc_type == RC_MOVE || $rc_type == RC_MOVE_OVER_REDIRECT ) { $lastLink = $this->message['last']; } else { $lastLink = $this->skin->makeKnownLinkObj( $rc->getTitle(), $this->message['last'], @@ -530,7 +582,7 @@ class EnhancedChangesList extends ChangesList { # Make user links if( $this->isDeleted($rc,Revision::DELETED_USER) ) { - $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-user') . '</span>'; + $rc->userlink = ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>'; } else { $rc->userlink = $this->skin->userLink( $rc_user, $rc_user_text ); $rc->usertalklink = $this->skin->userToolLinks( $rc_user, $rc_user_text ); @@ -555,8 +607,12 @@ class EnhancedChangesList extends ChangesList { if( !isset( $this->rc_cache[$secureName] ) ) { $this->rc_cache[$secureName] = array(); } + array_push( $this->rc_cache[$secureName], $rc ); } + + wfProfileOut( __METHOD__ ); + return $ret; } @@ -565,6 +621,9 @@ class EnhancedChangesList extends ChangesList { */ protected function recentChangesBlockGroup( $block ) { global $wgLang, $wgContLang, $wgRCShowChangedSize; + + wfProfileIn( __METHOD__ ); + $r = '<table cellpadding="0" cellspacing="0" border="0" style="background: none"><tr>'; # Collate list of users @@ -630,10 +689,10 @@ class EnhancedChangesList extends ChangesList { # onclick handler to toggle hidden/expanded $toggleLink = "onclick='toggleVisibility($jsid); return false'"; # Title for <a> tags - $expandTitle = htmlspecialchars( wfMsg('rc-enhanced-expand') ); - $closeTitle = htmlspecialchars( wfMsg('rc-enhanced-hide') ); + $expandTitle = htmlspecialchars( wfMsg( 'rc-enhanced-expand' ) ); + $closeTitle = htmlspecialchars( wfMsg( 'rc-enhanced-hide' ) ); - $tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>"; + $tl = "<span id='mw-rc-openarrow-$jsid' class='mw-changeslist-expanded' style='visibility:hidden'><a href='#' $toggleLink title='$expandTitle'>" . $this->sideArrow() . "</a></span>"; $tl .= "<span id='mw-rc-closearrow-$jsid' class='mw-changeslist-hidden' style='display:none'><a href='#' $toggleLink title='$closeTitle'>" . $this->downArrow() . "</a></span>"; $r .= '<td valign="top" style="white-space: nowrap"><tt>'.$tl.' '; @@ -645,7 +704,7 @@ class EnhancedChangesList extends ChangesList { # Article link if( $namehidden ) { - $r .= ' <span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>'; + $r .= ' <span class="history-deleted">' . wfMsgHtml( 'rev-deleted-event' ) . '</span>'; } else if( $allLogs ) { $r .= $this->maybeWatchedLink( $block[0]->link, $block[0]->watched ); } else { @@ -665,7 +724,7 @@ class EnhancedChangesList extends ChangesList { $r .= ' '; if( !$allLogs ) { $r .= '('; - if( !ChangesList::userCan($rcObj,Revision::DELETED_TEXT) ) { + if( !ChangesList::userCan( $rcObj, Revision::DELETED_TEXT ) ) { $r .= $nchanges[$n]; } else if( $isnew ) { $r .= $nchanges[$n]; @@ -720,6 +779,8 @@ class EnhancedChangesList extends ChangesList { # Extract fields from DB into the function scope (rc_xxxx variables) // FIXME: Would be good to replace this extract() call with something // that explicitly initializes variables. + # Classes to apply -- TODO implement + $classes = array(); extract( $rcObj->mAttribs ); #$r .= '<tr><td valign="top">'.$this->spacerArrow(); @@ -765,9 +826,14 @@ class EnhancedChangesList extends ChangesList { $r .= $rcObj->userlink; $r .= $rcObj->usertalklink; // log action - parent::insertAction( $r, $rcObj ); + $this->insertAction( $r, $rcObj ); // log comment - parent::insertComment( $r, $rcObj ); + $this->insertComment( $r, $rcObj ); + # Rollback + $this->insertRollback( $r, $rcObj ); + # Tags + $this->insertTags( $r, $rcObj, $classes ); + # Mark revision as deleted if( !$rc_log_type && $this->isDeleted($rcObj,Revision::DELETED_TEXT) ) { $r .= ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>'; @@ -778,6 +844,9 @@ class EnhancedChangesList extends ChangesList { $r .= "</table></div>\n"; $this->rcCacheIndex++; + + wfProfileOut( __METHOD__ ); + return $r; } @@ -804,7 +873,7 @@ class EnhancedChangesList extends ChangesList { protected function sideArrow() { global $wgContLang; $dir = $wgContLang->isRTL() ? 'l' : 'r'; - return $this->arrow( $dir, '+', wfMsg('rc-enhanced-expand') ); + return $this->arrow( $dir, '+', wfMsg( 'rc-enhanced-expand' ) ); } /** @@ -813,7 +882,7 @@ class EnhancedChangesList extends ChangesList { * @return string HTML <img> tag */ protected function downArrow() { - return $this->arrow( 'd', '-', wfMsg('rc-enhanced-hide') ); + return $this->arrow( 'd', '-', wfMsg( 'rc-enhanced-hide' ) ); } /** @@ -838,9 +907,13 @@ class EnhancedChangesList extends ChangesList { */ protected function recentChangesBlockLine( $rcObj ) { global $wgContLang, $wgRCShowChangedSize; + + wfProfileIn( __METHOD__ ); + # Extract fields from DB into the function scope (rc_xxxx variables) // FIXME: Would be good to replace this extract() call with something // that explicitly initializes variables. + $classes = array(); // TODO implement extract( $rcObj->mAttribs ); $curIdEq = "curid={$rc_cur_id}"; @@ -864,7 +937,7 @@ class EnhancedChangesList extends ChangesList { # Diff and hist links if ( $rc_type != RC_LOG ) { $r .= ' ('. $rcObj->difflink . $this->message['semicolon-separator']; - $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), wfMsg( 'hist' ), + $r .= $this->skin->makeKnownLinkObj( $rcObj->getTitle(), $this->message['hist'], $curIdEq.'&action=history' ) . ')'; } $r .= ' . . '; @@ -883,19 +956,17 @@ class EnhancedChangesList extends ChangesList { $this->skin, LogPage::extractParams($rc_params), true, true ); } } - # Edit or log comment - if( $rc_type != RC_MOVE && $rc_type != RC_MOVE_OVER_REDIRECT ) { - // log comment - if ( $this->isDeleted($rcObj,LogPage::DELETED_COMMENT) ) { - $r .= ' <span class="history-deleted">' . wfMsg('rev-deleted-comment') . '</span>'; - } else { - $r .= $this->skin->commentBlock( $rc_comment, $rcObj->getTitle() ); - } - } + $this->insertComment( $r, $rcObj ); + $this->insertRollback( $r, $rcObj ); + # Tags + $this->insertTags( $r, $rcObj, $classes ); # Show how many people are watching this if enabled $r .= $this->numberofWatchingusers($rcObj->numberofWatchingusers); $r .= "</td></tr></table>\n"; + + wfProfileOut( __METHOD__ ); + return $r; } @@ -907,6 +978,9 @@ class EnhancedChangesList extends ChangesList { if( count ( $this->rc_cache ) == 0 ) { return ''; } + + wfProfileIn( __METHOD__ ); + $blockOut = ''; foreach( $this->rc_cache as $block ) { if( count( $block ) < 2 ) { @@ -915,6 +989,9 @@ class EnhancedChangesList extends ChangesList { $blockOut .= $this->recentChangesBlockGroup( $block ); } } + + wfProfileOut( __METHOD__ ); + return '<div>'.$blockOut.'</div>'; } diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index ed68fe7a..19878f76 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -33,7 +33,7 @@ if ( !defined( 'MW_PHP4' ) ) { } /** MediaWiki version number */ -$wgVersion = '1.14.0'; +$wgVersion = '1.15.0'; /** Name of the site. It must be changed in LocalSettings.php */ $wgSitename = 'MediaWiki'; @@ -224,6 +224,10 @@ $wgFileStore['deleted']['hash'] = 3; ///< 3-level subdirectory split * equivalent to the corresponding member of $wgDBservers * tablePrefix Table prefix, the foreign wiki's $wgDBprefix * hasSharedCache True if the wiki's shared cache is accessible via the local $wgMemc + * + * ForeignAPIRepo: + * apibase Use for the foreign API's URL + * apiThumbCacheExpiry How long to locally cache thumbs for * * The default is to initialise these arrays from the MW<1.11 backwards compatible settings: * $wgUploadPath, $wgThumbnailScriptPath, $wgSharedUploadDirectory, etc. @@ -274,7 +278,8 @@ $wgUrlProtocols = array( 'nntp://', // @bug 3808 RFC 1738 'worldwind://', 'mailto:', - 'news:' + 'news:', + 'svn://', ); /** internal name of virus scanner. This servers as a key to the $wgAntivirusSetup array. @@ -521,6 +526,11 @@ $wgUserEmailUseReplyTo = false; $wgPasswordReminderResendTime = 24; /** + * The time, in seconds, when an emailed temporary password expires. + */ +$wgNewPasswordExpiry = 3600 * 24 * 7; + +/** * SMTP Mode * For using a direct (authenticated) SMTP server connection. * Default to false or fill an array : @@ -977,7 +987,7 @@ $wgReadOnly = null; $wgReadOnlyFile = false; ///< defaults to "{$wgUploadDirectory}/lock_yBgMBwiR"; /** - * Filename for debug logging. + * Filename for debug logging. See http://www.mediawiki.org/wiki/How_to_debug * The debug log file should be not be publicly accessible if it is used, as it * may contain private data. */ @@ -1029,6 +1039,13 @@ $wgDebugDumpSql = false; $wgDebugLogGroups = array(); /** + * Display debug data at the bottom of the main content area. + * + * Useful for developers and technical users trying to working on a closed wiki. + */ +$wgShowDebug = false; + +/** * Show the contents of $wgHooks in Special:Version */ $wgSpecialVersionShowHooks = false; @@ -1240,6 +1257,8 @@ $wgGroupPermissions['bureaucrat']['userrights'] = true; $wgGroupPermissions['bureaucrat']['noratelimit'] = true; // Permission to change users' groups assignments across wikis #$wgGroupPermissions['bureaucrat']['userrights-interwiki'] = true; +// Permission to export pages including linked pages regardless of $wgExportMaxLinkDepth +#$wgGroupPermissions['bureaucrat']['override-export-depth'] = true; #$wgGroupPermissions['sysop']['deleterevision'] = true; // To hide usernames from users and Sysops @@ -1287,7 +1306,7 @@ $wgGroupsRemoveFromSelf = array(); /** * Set of available actions that can be restricted via action=protect * You probably shouldn't change this. - * Translated trough restriction-* messages. + * Translated through restriction-* messages. */ $wgRestrictionTypes = array( 'edit', 'move' ); @@ -1349,6 +1368,10 @@ $wgAutoConfirmCount = 0; * array( APCOND_EMAILCONFIRMED ), *OR* * array( APCOND_EDITCOUNT, number of edits ), *OR* * array( APCOND_AGE, seconds since registration ), *OR* + * array( APCOND_INGROUPS, group1, group2, ... ), *OR* + * array( APCOND_ISIP, ip ), *OR* + * array( APCOND_IPINRANGE, range ), *OR* + * array( APCOND_AGE_FROM_EDIT, seconds since first edit ), *OR* * similar constructs defined by extensions. * * If $wgEmailAuthentication is off, APCOND_EMAILCONFIRMED will be true for any @@ -1446,7 +1469,7 @@ $wgCacheEpoch = '20030516000000'; * to ensure that client-side caches don't keep obsolete copies of global * styles. */ -$wgStyleVersion = '195'; +$wgStyleVersion = '207'; # Server-side caching: @@ -1974,6 +1997,7 @@ $wgMediaHandlers = array( 'image/jpeg' => 'BitmapHandler', 'image/png' => 'BitmapHandler', 'image/gif' => 'BitmapHandler', + 'image/tiff' => 'TiffHandler', 'image/x-ms-bmp' => 'BmpHandler', 'image/x-bmp' => 'BmpHandler', 'image/svg+xml' => 'SvgHandler', // official @@ -2052,6 +2076,16 @@ $wgMaxImageArea = 1.25e7; */ $wgMaxAnimatedGifArea = 1.0e6; /** + * Browsers don't support TIFF inline generally... + * For inline display, we need to convert to PNG or JPEG. + * Note scaling should work with ImageMagick, but may not with GD scaling. + * // PNG is lossless, but inefficient for photos + * $wgTiffThumbnailType = array( 'png', 'image/png' ); + * // JPEG is good for photos, but has no transparency support. Bad for diagrams. + * $wgTiffThumbnailType = array( 'jpg', 'image/jpeg' ); + */ +$wgTiffThumbnailType = false; +/** * If rendered thumbnail files are older than this timestamp, they * will be rerendered on demand as if the file didn't already exist. * Update if there is some need to force thumbs and SVG rasterizations @@ -2081,10 +2115,9 @@ $wgIgnoreImageErrors = false; */ $wgGenerateThumbnailOnParse = true; -/** Obsolete, always true, kept for compatibility with extensions */ +/** Whether or not to use image resizing */ $wgUseImageResize = true; - /** Set $wgCommandLineMode if it's not set already, to avoid notices */ if( !isset( $wgCommandLineMode ) ) { $wgCommandLineMode = false; @@ -2252,9 +2285,26 @@ $wgExportMaxHistory = 0; $wgExportAllowListContributors = false ; +/** + * If non-zero, Special:Export accepts a "pagelink-depth" parameter + * up to this specified level, which will cause it to include all + * pages linked to from the pages you specify. Since this number + * can become *insanely large* and could easily break your wiki, + * it's disabled by default for now. + * + * There's a HARD CODED limit of 5 levels of recursion to prevent a + * crazy-big export from being done by someone setting the depth + * number too high. In other words, last resort safety net. + */ +$wgExportMaxLinkDepth = 0; /** - * Edits matching these regular expressions in body text or edit summary + * Whether to allow the "export all pages in namespace" option + */ +$wgExportFromNamespaces = false; + +/** + * Edits matching these regular expressions in body text * will be recognised as spam and rejected automatically. * * There's no administrator override on-wiki, so be careful what you set. :) @@ -2264,6 +2314,9 @@ $wgExportAllowListContributors = false ; */ $wgSpamRegex = array(); +/** Same as the above except for edit summaries */ +$wgSummarySpamRegex = array(); + /** Similarly you can get a function to do the job. The function will be given * the following args: * - a Title object for the article the edit is made on @@ -2374,6 +2427,8 @@ $wgDefaultUserOptions = array( 'rclimit' => 50, 'wllimit' => 250, 'hideminor' => 0, + 'hidepatrolled' => 0, + 'newpageshidepatrolled' => 0, 'highlightbroken' => 1, 'stubthreshold' => 0, 'previewontop' => 1, @@ -2413,11 +2468,13 @@ $wgDefaultUserOptions = array( 'watchlisthideown' => 0, 'watchlisthideanons' => 0, 'watchlisthideliu' => 0, + 'watchlisthidepatrolled' => 0, 'watchcreations' => 0, 'watchdefault' => 0, 'watchmoves' => 0, 'watchdeletion' => 0, 'noconvertlink' => 0, + 'gender' => 'unknown', ); /** Whether or not to allow and use real name fields. Defaults to true. */ @@ -2504,7 +2561,7 @@ $wgAutoloadClasses = array(); * $wgExtensionCredits[$type][] = array( * 'name' => 'Example extension', * 'version' => 1.9, - * 'svn-revision' => '$LastChangedRevision: 47653 $', + * 'svn-revision' => '$LastChangedRevision: 51678 $', * 'author' => 'Foo Barstein', * 'url' => 'http://wwww.example.com/Example%20Extension/', * 'description' => 'An example extension', @@ -2724,6 +2781,9 @@ $wgBrowserBlackList = array( * * This variable is currently used ONLY for signature formatting, not for * anything else. + * + * Timezones can be translated by editing MediaWiki messages of type + * timezone-nameinlowercase like timezone-utc. */ # $wgLocaltimezone = 'GMT'; # $wgLocaltimezone = 'PST8PDT'; @@ -2754,17 +2814,17 @@ $wgLocalTZoffset = null; /** - * When translating messages with wfMsg(), it is not always clear what should be - * considered UI messages and what shoud be content messages. + * When translating messages with wfMsg(), it is not always clear what should + * be considered UI messages and what should be content messages. * - * For example, for regular wikipedia site like en, there should be only one - * 'mainpage', therefore when getting the link of 'mainpage', we should treate - * it as content of the site and call wfMsgForContent(), while for rendering the - * text of the link, we call wfMsg(). The code in default behaves this way. - * However, sites like common do offer different versions of 'mainpage' and the - * like for different languages. This array provides a way to override the - * default behavior. For example, to allow language specific mainpage and - * community portal, set + * For example, for the English Wikipedia, there should be only one 'mainpage', + * so when getting the link for 'mainpage', we should treat it as site content + * and call wfMsgForContent(), but for rendering the text of the link, we call + * wfMsg(). The code behaves this way by default. However, sites like the + * Wikimedia Commons do offer different versions of 'mainpage' and the like for + * different languages. This array provides a way to override the default + * behavior. For example, to allow language-specific main page and community + * portal, set * * $wgForceUIMsgAsContentMsg = array( 'mainpage', 'portal-url' ); */ @@ -2965,6 +3025,7 @@ $wgSpecialPageGroups = array( 'Newimages' => 'changes', 'Newpages' => 'changes', 'Log' => 'changes', + 'Tags' => 'changes', 'Upload' => 'media', 'Listfiles' => 'media', @@ -3073,6 +3134,19 @@ $wgNoFollowLinks = true; $wgNoFollowNsExceptions = array(); /** + * If this is set to an array of domains, external links to these domain names + * (or any subdomains) will not be set to rel="nofollow" regardless of the + * value of $wgNoFollowLinks. For instance: + * + * $wgNoFollowDomainExceptions = array( 'en.wikipedia.org', 'wiktionary.org' ); + * + * This would add rel="nofollow" to links to de.wikipedia.org, but not + * en.wikipedia.org, wiktionary.org, en.wiktionary.org, us.en.wikipedia.org, + * etc. + */ +$wgNoFollowDomainExceptions = array(); + +/** * Default robot policy. The default policy is to encourage indexing and fol- * lowing of links. It may be overridden on a per-namespace and/or per-page * basis. @@ -3216,6 +3290,12 @@ $wgRateLimitLog = null; $wgRateLimitsExcludedGroups = array(); /** + * Array of IPs which should be excluded from rate limits. + * This may be useful for whitelisting NAT gateways for conferences, etc. + */ +$wgRateLimitsExcludedIPs = array(); + +/** * On Special:Unusedimages, consider images "used", if they are put * into a category. Default (false) is not to count those as used. */ @@ -3490,6 +3570,18 @@ $wgAPIListModules = array(); $wgAPIMaxDBRows = 5000; /** + * The maximum size (in bytes) of an API result. + * Don't set this lower than $wgMaxArticleSize*1024 + */ +$wgAPIMaxResultSize = 8388608; + +/** + * The maximum number of uncached diffs that can be retrieved in one API + * request. Set this to 0 to disable API diffs altogether + */ +$wgAPIMaxUncachedDiffs = 1; + +/** * Parser test suite files to be run by parserTests.php when no specific * filename is passed to it. * @@ -3600,6 +3692,25 @@ $wgMaximumMovedPages = 100; $wgFixDoubleRedirects = false; /** + * Max number of redirects to follow when resolving redirects. + * 1 means only the first redirect is followed (default behavior). + * 0 or less means no redirects are followed. + */ +$wgMaxRedirects = 1; + +/** + * Array of invalid page redirect targets. + * Attempting to create a redirect to any of the pages in this array + * will make the redirect fail. + * Userlogout is hard-coded, so it does not need to be listed here. + * (bug 10569) Disallow Mypage and Mytalk as well. + * + * As of now, this only checks special pages. Redirects to pages in + * other namespaces cannot be invalidated by this variable. + */ +$wgInvalidRedirectTargets = array( 'Filepath', 'Mypage', 'Mytalk' ); + +/** * Array of namespaces to generate a sitemap for when the * maintenance/generateSitemap.php script is run, or false if one is to be ge- * nerated for all namespaces. @@ -3626,10 +3737,10 @@ $wgPasswordAttemptThrottle = array( 'count' => 5, 'seconds' => 300 ); $wgEdititis = false; /** -* Enable the UniversalEditButton for browsers that support it -* (currently only Firefox with an extension) -* See http://universaleditbutton.org for more background information -*/ + * Enable the UniversalEditButton for browsers that support it + * (currently only Firefox with an extension) + * See http://universaleditbutton.org for more background information + */ $wgUniversalEditButton = true; /** @@ -3638,3 +3749,45 @@ $wgUniversalEditButton = true; * and the functionality will be enabled universally. */ $wgEnforceHtmlIds = true; + +/** + * Search form behavior + * true = use Go & Search buttons + * false = use Go button & Advanced search link + */ +$wgUseTwoButtonsSearchForm = true; + +/** + * Preprocessor caching threshold + */ +$wgPreprocessorCacheThreshold = 1000; + +/** + * Allow filtering by change tag in recentchanges, history, etc + * Has no effect if no tags are defined in valid_tag. + */ +$wgUseTagFilter = true; + +/** + * Allow redirection to another page when a user logs in. + * To enable, set to a string like 'Main Page' + */ +$wgRedirectOnLogin = null; + +/** + * Characters to prevent during new account creations. + * This is used in a regular expression character class during + * registration (regex metacharacters like / are escaped). + */ +$wgInvalidUsernameCharacters = '@'; + +/** + * Character used as a delimiter when testing for interwiki userrights + * (In Special:UserRights, it is possible to modify users on different + * databases if the delimiter is used, e.g. Someuser@enwiki). + * + * It is recommended that you have this delimiter in + * $wgInvalidUsernameCharacters above, or you will not be able to + * modify the user rights of those users via Special:UserRights + */ +$wgUserrightsInterwikiDelimiter = '@'; diff --git a/includes/EditPage.php b/includes/EditPage.php index 0193dc38..3589b52d 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -84,6 +84,7 @@ class EditPage { /* $didSave should be set to true whenever an article was succesfully altered. */ public $didSave = false; + public $undidRev = 0; public $suppressIntro = false; @@ -164,35 +165,28 @@ class EditPage { $undorev->getPage() == $this->mArticle->getID() && !$undorev->isDeleted( Revision::DELETED_TEXT ) && !$oldrev->isDeleted( Revision::DELETED_TEXT ) ) { - $undorev_text = $undorev->getText(); - $oldrev_text = $oldrev->getText(); - $currev_text = $text; - - if ( $currev_text != $undorev_text ) { - $result = wfMerge( $undorev_text, $oldrev_text, $currev_text, $text ); + + $undotext = $this->mArticle->getUndoText( $undorev, $oldrev ); + if ( $undotext === false ) { + # Warn the user that something went wrong + $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-failure">' . wfMsgNoTrans( 'undo-failure' ) . '</div>' ); } else { - # No use doing a merge if it's just a straight revert. - $text = $oldrev_text; - $result = true; - } - if ( $result ) { + $text = $undotext; # Inform the user of our success and set an automatic edit summary - $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-success' ) ); + $this->editFormPageTop .= $wgOut->parse( '<div class="mw-undo-success">' . wfMsgNoTrans( 'undo-success' ) . '</div>' ); $firstrev = $oldrev->getNext(); # If we just undid one rev, use an autosummary if ( $firstrev->mId == $undo ) { - $this->summary = wfMsgForContent('undo-summary', $undo, $undorev->getUserText()); + $this->summary = wfMsgForContent( 'undo-summary', $undo, $undorev->getUserText() ); + $this->undidRev = $undo; } $this->formtype = 'diff'; - } else { - # Warn the user that something went wrong - $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-failure' ) ); } } else { // Failed basic sanity checks. // Older revisions may have been removed since the link // was created, or we may simply have got bogus input. - $this->editFormPageTop .= $wgOut->parse( wfMsgNoTrans( 'undo-norev' ) ); + $this->editFormPageTop .= $wgOut->parse( '<div class="error mw-undo-norev">' . wfMsgNoTrans( 'undo-norev' ) . '</div>' ); } } else if ( $section != '' ) { if ( $section == 'new' ) { @@ -330,7 +324,7 @@ class EditPage { protected function wasDeletedSinceLastEdit() { if ( $this->deletedSinceEdit ) return true; - if ( $this->mTitle->isDeleted() ) { + if ( $this->mTitle->isDeletedQuick() ) { $this->lastDelete = $this->getLastDelete(); if ( $this->lastDelete ) { $deleteTime = wfTimestamp( TS_MW, $this->lastDelete->log_timestamp ); @@ -409,6 +403,11 @@ class EditPage { } } } + + // If they used redlink=1 and the page exists, redirect to the main article + if ( $wgRequest->getBool( 'redlink' ) && $this->mTitle->exists() ) { + $wgOut->redirect( $this->mTitle->getFullURL() ); + } wfProfileIn( __METHOD__."-business-end" ); @@ -427,7 +426,6 @@ class EditPage { # Optional notices on a per-namespace and per-page basis $editnotice_ns = 'editnotice-'.$this->mTitle->getNamespace(); - $editnotice_page = $editnotice_ns.'-'.$this->mTitle->getDBkey(); if ( !wfEmptyMsg( $editnotice_ns, wfMsgForContent( $editnotice_ns ) ) ) { $wgOut->addWikiText( wfMsgForContent( $editnotice_ns ) ); } @@ -440,8 +438,6 @@ class EditPage { $wgOut->addWikiText( wfMsgForContent( $editnotice_base ) ); } } - } else if ( !wfEmptyMsg( $editnotice_page, wfMsgForContent( $editnotice_page ) ) ) { - $wgOut->addWikiText( wfMsgForContent( $editnotice_page ) ); } # Attempt submission here. This will check for edit conflicts, @@ -529,7 +525,7 @@ class EditPage { } elseif ( $this->section == 'new' ) { // Nothing *to* preview for new sections return false; - } elseif ( ( $wgRequest->getVal( 'preload' ) !== '' || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) { + } elseif ( ( $wgRequest->getVal( 'preload' ) !== null || $this->mTitle->exists() ) && $wgUser->getOption( 'previewonfirst' ) ) { // Standard preference behaviour return true; } elseif ( !$this->mTitle->exists() && $this->mTitle->getNamespace() == NS_CATEGORY ) { @@ -560,7 +556,7 @@ class EditPage { $this->textbox2 = $this->safeUnicodeInput( $request, 'wpTextbox2' ); $this->mMetaData = rtrim( $request->getText( 'metadata' ) ); # Truncate for whole multibyte characters. +5 bytes for ellipsis - $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250 ); + $this->summary = $wgLang->truncate( $request->getText( 'wpSummary' ), 250, '' ); # Remove extra headings from summaries and new sections. $this->summary = preg_replace('/^\s*=+\s*(.*?)\s*=+\s*$/', '$1', $this->summary); @@ -574,7 +570,7 @@ class EditPage { # If the form is incomplete, force to preview. wfDebug( "$fname: Form data appears to be incomplete\n" ); wfDebug( "POST DATA: " . var_export( $_POST, true ) . "\n" ); - $this->preview = true; + $this->preview = true; } else { /* Fallback for live preview */ $this->preview = $request->getCheck( 'wpPreview' ) || $request->getCheck( 'wpLivePreview' ); @@ -644,6 +640,13 @@ class EditPage { if ( $this->section == 'new' && $request->getVal( 'preloadtitle' ) ) { $this->summary = $request->getVal( 'preloadtitle' ); } + elseif ( $this->section != 'new' && $request->getVal( 'summary' ) ) { + $this->summary = $request->getText( 'summary' ); + } + + if ( $request->getVal( 'minor' ) ) { + $this->minoredit = true; + } } $this->oldid = $request->getInt( 'oldid' ); @@ -677,8 +680,16 @@ class EditPage { if ( $this->suppressIntro ) { return; } + + $namespace = $this->mTitle->getNamespace(); + + if ( $namespace == NS_MEDIAWIKI ) { + # Show a warning if editing an interface message + $wgOut->wrapWikiMsg( "<div class='mw-editinginterface'>\n$1</div>", 'editinginterface' ); + } + # Show a warning message when someone creates/edits a user (talk) page but the user does not exists - if ( $this->mTitle->getNamespace() == NS_USER || $this->mTitle->getNamespace() == NS_USER_TALK ) { + if ( $namespace == NS_USER || $namespace == NS_USER_TALK ) { $parts = explode( '/', $this->mTitle->getText(), 2 ); $username = $parts[0]; $id = User::idFromName( $username ); @@ -737,7 +748,7 @@ class EditPage { if ( !wfRunHooks( 'EditPage::attemptSave', array( &$this ) ) ) { - wfDebug( "Hook 'EditPage::attemptSave' aborted article saving" ); + wfDebug( "Hook 'EditPage::attemptSave' aborted article saving\n" ); return self::AS_HOOK_ERROR; } @@ -757,7 +768,7 @@ class EditPage { $this->mMetaData = '' ; # Check for spam - $match = self::matchSpamRegex( $this->summary ); + $match = self::matchSummarySpamRegex( $this->summary ); if ( $match === false ) { $match = self::matchSpamRegex( $this->textbox1 ); } @@ -859,11 +870,20 @@ class EditPage { wfProfileOut( $fname ); return self::AS_HOOK_ERROR; } + + # Handle the user preference to force summaries here. Check if it's not a redirect. + if ( !$this->allowBlankSummary && !Title::newFromRedirect( $this->textbox1 ) ) { + if ( md5( $this->summary ) == $this->autoSumm ) { + $this->missingSummary = true; + wfProfileOut( $fname ); + return self::AS_SUMMARY_NEEDED; + } + } $isComment = ( $this->section == 'new' ); $this->mArticle->insertNewArticle( $this->textbox1, $this->summary, - $this->minoredit, $this->watchthis, false, $isComment, $bot); + $this->minoredit, $this->watchthis, false, $isComment, $bot ); wfProfileOut( $fname ); return self::AS_SUCCESS_NEW_ARTICLE; @@ -893,39 +913,35 @@ class EditPage { } } $userid = $wgUser->getId(); + + # Suppress edit conflict with self, except for section edits where merging is required. + if ( $this->isConflict && $this->section == '' && $this->userWasLastToEdit($userid,$this->edittime) ) { + wfDebug( "EditPage::editForm Suppressing edit conflict, same user.\n" ); + $this->isConflict = false; + } if ( $this->isConflict ) { wfDebug( "EditPage::editForm conflict! getting section '$this->section' for time '$this->edittime' (article time '" . $this->mArticle->getTimestamp() . "')\n" ); - $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime); - } - else { + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary, $this->edittime ); + } else { wfDebug( "EditPage::editForm getting section '$this->section'\n" ); - $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary); + $text = $this->mArticle->replaceSection( $this->section, $this->textbox1, $this->summary ); } if ( is_null( $text ) ) { wfDebug( "EditPage::editForm activating conflict; section replace failed.\n" ); $this->isConflict = true; - $text = $this->textbox1; - } - - # Suppress edit conflict with self, except for section edits where merging is required. - if ( $this->section == '' && $userid && $this->userWasLastToEdit($userid,$this->edittime) ) { - wfDebug( "EditPage::editForm Suppressing edit conflict, same user.\n" ); - $this->isConflict = false; - } else { - # switch from section editing to normal editing in edit conflict - if ( $this->isConflict ) { - # Attempt merge - if ( $this->mergeChangesInto( $text ) ) { - // Successful merge! Maybe we should tell the user the good news? - $this->isConflict = false; - wfDebug( "EditPage::editForm Suppressing edit conflict, successful merge.\n" ); - } else { - $this->section = ''; - $this->textbox1 = $text; - wfDebug( "EditPage::editForm Keeping edit conflict, failed merge.\n" ); - } + $text = $this->textbox1; // do not try to merge here! + } else if ( $this->isConflict ) { + # Attempt merge + if ( $this->mergeChangesInto( $text ) ) { + // Successful merge! Maybe we should tell the user the good news? + $this->isConflict = false; + wfDebug( "EditPage::editForm Suppressing edit conflict, successful merge.\n" ); + } else { + $this->section = ''; + $this->textbox1 = $text; + wfDebug( "EditPage::editForm Keeping edit conflict, failed merge.\n" ); } } @@ -944,9 +960,9 @@ class EditPage { } # Handle the user preference to force summaries here, but not for null edits - if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp($oldtext, $text) && - !is_object( Title::newFromRedirect( $text ) ) # check if it's not a redirect - ) { + if ( $this->section != 'new' && !$this->allowBlankSummary && 0 != strcmp($oldtext,$text) + && !Title::newFromRedirect( $text ) ) # check if it's not a redirect + { if ( md5( $this->summary ) == $this->autoSumm ) { $this->missingSummary = true; wfProfileOut( $fname ); @@ -1008,7 +1024,8 @@ class EditPage { # update the article here if ( $this->mArticle->updateArticle( $text, $this->summary, $this->minoredit, - $this->watchthis, $bot, $sectionanchor ) ) { + $this->watchthis, $bot, $sectionanchor ) ) + { wfProfileOut( $fname ); return self::AS_SUCCESS_UPDATE; } else { @@ -1024,6 +1041,7 @@ class EditPage { * 50 revisions for the sake of performance. */ protected function userWasLastToEdit( $id, $edittime ) { + if( !$id ) return false; $dbw = wfGetDB( DB_MASTER ); $res = $dbw->select( 'revision', 'rev_user', @@ -1047,14 +1065,26 @@ class EditPage { */ public static function matchSpamRegex( $text ) { global $wgSpamRegex; - if ( $wgSpamRegex ) { - // For back compatibility, $wgSpamRegex may be a single string or an array of regexes. - $regexes = (array)$wgSpamRegex; - foreach( $regexes as $regex ) { - $matches = array(); - if ( preg_match( $regex, $text, $matches ) ) { - return $matches[0]; - } + // For back compatibility, $wgSpamRegex may be a single string or an array of regexes. + $regexes = (array)$wgSpamRegex; + return self::matchSpamRegexInternal( $text, $regexes ); + } + + /** + * Check given input text against $wgSpamRegex, and return the text of the first match. + * @return mixed -- matching string or false + */ + public static function matchSummarySpamRegex( $text ) { + global $wgSummarySpamRegex; + $regexes = (array)$wgSummarySpamRegex; + return self::matchSpamRegexInternal( $text, $regexes ); + } + + protected static function matchSpamRegexInternal( $text, $regexes ) { + foreach( $regexes as $regex ) { + $matches = array(); + if( preg_match( $regex, $text, $matches ) ) { + return $matches[0]; } } return false; @@ -1133,7 +1163,7 @@ class EditPage { $wgOut->setArticleRelated( true ); if ( $this->isConflict ) { - $wgOut->addWikiMsg( 'explainconflict' ); + $wgOut->wrapWikiMsg( "<div class='mw-explainconflict'>\n$1</div>", 'explainconflict' ); $this->textbox2 = $this->textbox1; $this->textbox1 = $this->getContent(); @@ -1142,9 +1172,7 @@ class EditPage { if ( $this->section != '' && $this->section != 'new' ) { $matches = array(); if ( !$this->summary && !$this->preview && !$this->diff ) { - preg_match( "/^(=+)(.+)\\1/mi", - $this->textbox1, - $matches ); + preg_match( "/^(=+)(.+)\\1/mi", $this->textbox1, $matches ); if ( !empty( $matches[2] ) ) { global $wgParser; $this->summary = "/* " . @@ -1155,7 +1183,7 @@ class EditPage { } if ( $this->missingComment ) { - $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' ); + $wgOut->wrapWikiMsg( '<div id="mw-missingcommenttext">$1</div>', 'missingcommenttext' ); } if ( $this->missingSummary && $this->section != 'new' ) { @@ -1177,9 +1205,9 @@ class EditPage { // Let sysop know that this will make private content public if saved if ( !$this->mArticle->mRevision->userCan( Revision::DELETED_TEXT ) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); } else if ( $this->mArticle->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); } if ( !$this->mArticle->mRevision->isCurrent() ) { @@ -1208,8 +1236,6 @@ class EditPage { $classes = array(); // Textarea CSS if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { - # Show a warning if editing an interface message - $wgOut->addWikiMsg( 'editinginterface' ); } elseif ( $this->mTitle->isProtected( 'edit' ) ) { # Is the title semi-protected? if ( $this->mTitle->isSemiProtected() ) { @@ -1228,17 +1254,19 @@ class EditPage { if ( $this->mTitle->isCascadeProtected() ) { # Is this page under cascading protection from some source pages? list($cascadeSources, /* $restrictions */) = $this->mTitle->getCascadeProtectionSources(); - $notice = "$1\n"; - if ( count($cascadeSources) > 0 ) { + $notice = "<div class='mw-cascadeprotectedwarning'>$1\n"; + $cascadeSourcesCount = count( $cascadeSources ); + if ( $cascadeSourcesCount > 0 ) { # Explain, and list the titles responsible foreach( $cascadeSources as $page ) { $notice .= '* [[:' . $page->getPrefixedText() . "]]\n"; } } - $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', count($cascadeSources) ) ); + $notice .= '</div>'; + $wgOut->wrapWikiMsg( $notice, array( 'cascadeprotectedwarning', $cascadeSourcesCount ) ); } if ( !$this->mTitle->exists() && $this->mTitle->getRestrictions( 'create' ) ) { - $wgOut->addWikiMsg( 'titleprotectedwarning' ); + $wgOut->wrapWikiMsg( '<div class="mw-titleprotectedwarning">$1</div>', 'titleprotectedwarning' ); } if ( $this->kblength === false ) { @@ -1263,6 +1291,7 @@ class EditPage { $cancel = $sk->makeKnownLink( $wgTitle->getPrefixedText(), wfMsgExt('cancel', array('parseinline')) ); + $separator = wfMsgExt( 'pipe-separator' , 'escapenoentities' ); $edithelpurl = Skin::makeInternalOrExternalUrl( wfMsgForContent( 'edithelppage' )); $edithelp = '<a target="helpwindow" href="'.$edithelpurl.'">'. htmlspecialchars( wfMsg( 'edithelp' ) ).'</a> '. @@ -1318,7 +1347,7 @@ class EditPage { # if this is a comment, show a subject line at the top, which is also the edit summary. # Otherwise, show a summary field at the bottom - $summarytext = htmlspecialchars( $wgContLang->recodeForEdit( $this->summary ) ); # FIXME + $summarytext = $wgContLang->recodeForEdit( $this->summary ); # If a blank edit summary was previously provided, and the appropriate # user preference is active, pass a hidden tag as wpIgnoreBlankSummary. This will stop the @@ -1332,7 +1361,26 @@ class EditPage { $autosumm = $this->autoSumm ? $this->autoSumm : md5( $this->summary ); $summaryhiddens .= Xml::hidden( 'wpAutoSummary', $autosumm ); if ( $this->section == 'new' ) { - $commentsubject="<span id='wpSummaryLabel'><label for='wpSummary'>{$subject}</label></span>\n<input tabindex='1' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' />{$summaryhiddens}<br />"; + $commentsubject = ''; + if ( !$wgRequest->getBool( 'nosummary' ) ) { + # Add a class if 'missingsummary' is triggered to allow styling of the summary line + $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; + + $commentsubject = + Xml::tags( 'label', array( 'for' => 'wpSummary' ), $subject ); + $commentsubject = + Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ), + $commentsubject ); + $commentsubject .= ' '; + $commentsubject .= Xml::input( 'wpSummary', + 60, + $summarytext, + array( + 'id' => 'wpSummary', + 'maxlength' => '200', + 'tabindex' => '1' + ) ); + } $editsummary = "<div class='editOptions'>\n"; global $wgParser; $formattedSummary = wfMsgForContent( 'newsectionsummary', $wgParser->stripSectionName( $this->summary ) ); @@ -1340,10 +1388,39 @@ class EditPage { $summarypreview = ''; } else { $commentsubject = ''; - $editsummary="<div class='editOptions'>\n<span id='wpSummaryLabel'><label for='wpSummary'>{$summary}</label></span>\n<input tabindex='2' type='text' value=\"$summarytext\" name='wpSummary' id='wpSummary' maxlength='200' size='60' />{$summaryhiddens}<br />"; - $summarypreview = $summarytext && $this->preview ? "<div class=\"mw-summary-preview\">". wfMsg('summary-preview') .$sk->commentBlock( $this->summary, $this->mTitle )."</div>\n" : ''; + + # Add a class if 'missingsummary' is triggered to allow styling of the summary line + $summaryClass = $this->missingSummary ? 'mw-summarymissed' : 'mw-summary'; + + $editsummary = Xml::tags( 'label', array( 'for' => 'wpSummary' ), $summary ); + $editsummary = Xml::tags( 'span', array( 'class' => $summaryClass, 'id' => "wpSummaryLabel" ), + $editsummary ) . ' '; + + $editsummary .= Xml::input( 'wpSummary', + 60, + $summarytext, + array( + 'id' => 'wpSummary', + 'maxlength' => '200', + 'tabindex' => '1' + ) ); + + // No idea where this is closed. + $editsummary = Xml::openElement( 'div', array( 'class' => 'editOptions' ) ) + . $editsummary . '<br/>'; + + $summarypreview = ''; + if ( $summarytext && $this->preview ) { + $summarypreview = + Xml::tags( 'div', + array( 'class' => 'mw-summary-preview' ), + wfMsg( 'summary-preview' ) . + $sk->commentBlock( $this->summary, $this->mTitle ) + ); + } $subjectpreview = ''; } + $commentsubject .= $summaryhiddens; # Set focus to the edit box on load, except on preview or diff, where it would interfere with the display if ( !$this->preview && !$this->diff ) { @@ -1373,15 +1450,18 @@ class EditPage { $recreate = ''; if ( $this->wasDeletedSinceLastEdit() ) { if ( 'save' != $this->formtype ) { - $wgOut->addWikiMsg('deletedwhileediting'); + $wgOut->wrapWikiMsg( + "<div class='error mw-deleted-while-editing'>\n$1</div>", + 'deletedwhileediting' ); } else { - // Hide the toolbar and edit area, use can click preview to get it back + // Hide the toolbar and edit area, user can click preview to get it back // Add an confirmation checkbox and explanation. $toolbar = ''; - $recreate = $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment )); - $recreate .= - "<br /><input tabindex='1' type='checkbox' value='1' name='wpRecreate' id='wpRecreate' />". - "<label for='wpRecreate' title='".wfMsg('tooltip-recreate')."'>". wfMsg('recreate')."</label>"; + $recreate = '<div class="mw-confirm-recreate">' . + $wgOut->parse( wfMsg( 'confirmrecreate', $this->lastDelete->user_name , $this->lastDelete->log_comment ) ) . + Xml::checkLabel( wfMsg( 'recreate' ), 'wpRecreate', 'wpRecreate', false, + array( 'title' => $sk->titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ) + ) . '</div>'; } } @@ -1436,7 +1516,7 @@ END $wgOut->addHTML( "<div class='editButtons'> {$buttonshtml} - <span class='editHelp'>{$cancel} | {$edithelp}</span> + <span class='editHelp'>{$cancel}{$separator}{$edithelp}</span> </div><!-- editButtons --> </div><!-- editOptions -->"); @@ -1606,7 +1686,7 @@ END $wgOut->addHTML( '</div>' ); } - function getLastDelete() { + protected function getLastDelete() { $dbr = wfGetDB( DB_SLAVE ); $data = $dbr->selectRow( array( 'logging', 'user' ), @@ -1618,15 +1698,23 @@ END 'log_title', 'log_comment', 'log_params', - 'user_name', ), + 'log_deleted', + 'user_name' ), array( 'log_namespace' => $this->mTitle->getNamespace(), 'log_title' => $this->mTitle->getDBkey(), 'log_type' => 'delete', 'log_action' => 'delete', 'user_id=log_user' ), __METHOD__, - array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ) ); - + array( 'LIMIT' => 1, 'ORDER BY' => 'log_timestamp DESC' ) + ); + // Quick paranoid permission checks... + if( is_object($data) ) { + if( $data->log_deleted & LogPage::DELETED_USER ) + $data->user_name = wfMsgHtml('rev-deleted-user'); + if( $data->log_deleted & LogPage::DELETED_COMMENT ) + $data->log_comment = wfMsgHtml('rev-deleted-comment'); + } return $data; } @@ -1651,6 +1739,8 @@ END $parserOptions = ParserOptions::newFromUser( $wgUser ); $parserOptions->setEditSection( false ); + $parserOptions->setIsPreview( true ); + $parserOptions->setIsSectionPreview( !is_null($this->section) && $this->section !== '' ); global $wgRawHtml; if ( $wgRawHtml && !$this->mTokenOk ) { @@ -1672,7 +1762,7 @@ END $parserOptions->setTidy(true); $parserOutput = $wgParser->parse( $previewtext, $this->mTitle, $parserOptions ); $previewHTML = $parserOutput->mText; - } elseif ( $rt = Title::newFromRedirect( $this->textbox1 ) ) { + } elseif ( $rt = Title::newFromRedirectArray( $this->textbox1 ) ) { $previewHTML = $this->mArticle->viewRedirect( $rt, false ); } else { $toparse = $this->textbox1; @@ -1834,8 +1924,7 @@ END $baseText = $baseRevision->getText(); // The current state, we want to merge updates into it - $currentRevision = Revision::loadFromTitle( - $db, $this->mTitle ); + $currentRevision = Revision::loadFromTitle( $db, $this->mTitle ); if ( is_null( $currentRevision ) ) { wfProfileOut( $fname ); return false; @@ -2389,7 +2478,9 @@ END global $wgUser, $wgOut, $wgTitle, $wgRequest; $resultDetails = false; - $value = $this->internalAttemptSave( $resultDetails, $wgUser->isAllowed('bot') && $wgRequest->getBool('bot', true) ); + # Allow bots to exempt some edits from bot flagging + $bot = $wgUser->isAllowed('bot') && $wgRequest->getBool('bot',true); + $value = $this->internalAttemptSave( $resultDetails, $bot ); if ( $value == self::AS_SUCCESS_UPDATE || $value == self::AS_SUCCESS_NEW_ARTICLE ) { $this->didSave = true; diff --git a/includes/EnotifNotifyJob.php b/includes/EnotifNotifyJob.php index 31fcb0d5..f7178d0f 100644 --- a/includes/EnotifNotifyJob.php +++ b/includes/EnotifNotifyJob.php @@ -26,7 +26,8 @@ class EnotifNotifyJob extends Job { $this->params['timestamp'], $this->params['summary'], $this->params['minorEdit'], - $this->params['oldid'] + $this->params['oldid'], + $this->params['watchers'] ); return true; } diff --git a/includes/Exception.php b/includes/Exception.php index eb715986..5f808b20 100644 --- a/includes/Exception.php +++ b/includes/Exception.php @@ -161,23 +161,26 @@ class MWException extends Exception { if( $hookResult = $this->runHooks( get_class( $this ) . "Raw" ) ) { die( $hookResult ); } - echo $this->htmlHeader(); - echo $this->getHTML(); - echo $this->htmlFooter(); + if ( defined( 'MEDIAWIKI_INSTALL' ) || $this->htmlBodyOnly() ) { + echo $this->getHTML(); + } else { + echo $this->htmlHeader(); + echo $this->getHTML(); + echo $this->htmlFooter(); + } } } /** * Output a report about the exception and takes care of formatting. - * It will be either HTML or plain text based on $wgCommandLineMode. + * It will be either HTML or plain text based on isCommandLine(). */ function report() { - global $wgCommandLineMode; $log = $this->getLogMessage(); if ( $log ) { wfDebugLog( 'exception', $log ); } - if ( $wgCommandLineMode ) { + if ( self::isCommandLine() ) { wfPrintError( $this->getText() ); } else { $this->reportHTML(); @@ -204,7 +207,7 @@ class MWException extends Exception { <title>$title</title> </head> <body> - <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''>$title</h1> + <h1><img src='$wgLogo' style='float:left;margin-right:1em' alt=''/>$title</h1> "; } @@ -214,6 +217,17 @@ class MWException extends Exception { function htmlFooter() { echo "</body></html>"; } + + /** + * headers handled by subclass? + */ + function htmlBodyOnly() { + return false; + } + + static function isCommandLine() { + return !empty( $GLOBALS['wgCommandLineMode'] ) && !defined( 'MEDIAWIKI_INSTALL' ); + } } /** @@ -264,41 +278,44 @@ function wfInstallExceptionHandler() { * Report an exception to the user */ function wfReportException( Exception $e ) { - if ( $e instanceof MWException ) { - try { - $e->report(); - } catch ( Exception $e2 ) { - // Exception occurred from within exception handler - // Show a simpler error message for the original exception, - // don't try to invoke report() - $message = "MediaWiki internal error.\n\n" . - "Original exception: " . $e->__toString() . - "\n\nException caught inside exception handler: " . - $e2->__toString() . "\n"; - - if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) { - wfPrintError( $message ); - } else { - echo nl2br( htmlspecialchars( $message ) ). "\n"; - } - } - } else { - $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" . - $e->__toString() . "\n"; - if ( $GLOBALS['wgShowExceptionDetails'] ) { - $message .= "\n" . $e->getTraceAsString() ."\n"; - } - if ( !empty( $GLOBALS['wgCommandLineMode'] ) ) { - wfPrintError( $message ); - } else { - echo nl2br( htmlspecialchars( $message ) ). "\n"; - } - } + $cmdLine = MWException::isCommandLine(); + if ( $e instanceof MWException ) { + try { + $e->report(); + } catch ( Exception $e2 ) { + // Exception occurred from within exception handler + // Show a simpler error message for the original exception, + // don't try to invoke report() + $message = "MediaWiki internal error.\n\n"; + if ( $GLOBALS['wgShowExceptionDetails'] ) + $message .= "Original exception: " . $e->__toString(); + $message .= "\n\nException caught inside exception handler"; + if ( $GLOBALS['wgShowExceptionDetails'] ) + $message .= ": " . $e2->__toString(); + $message .= "\n"; + if ( $cmdLine ) { + wfPrintError( $message ); + } else { + echo nl2br( htmlspecialchars( $message ) ). "\n"; + } + } + } else { + $message = "Unexpected non-MediaWiki exception encountered, of type \"" . get_class( $e ) . "\"\n" . + $e->__toString() . "\n"; + if ( $GLOBALS['wgShowExceptionDetails'] ) { + $message .= "\n" . $e->getTraceAsString() ."\n"; + } + if ( $cmdLine ) { + wfPrintError( $message ); + } else { + echo nl2br( htmlspecialchars( $message ) ). "\n"; + } + } } /** * Print a message, if possible to STDERR. - * Use this in command line mode only (see wgCommandLineMode) + * Use this in command line mode only (see isCommandLine) */ function wfPrintError( $message ) { #NOTE: STDERR may not be available, especially if php-cgi is used from the command line (bug #15602). diff --git a/includes/Exif.php b/includes/Exif.php index d5cf09cf..9e54bd55 100644 --- a/includes/Exif.php +++ b/includes/Exif.php @@ -1,10 +1,5 @@ <?php /** - * @ingroup Media - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License - * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or @@ -20,7 +15,12 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * http://www.gnu.org/copyleft/gpl.html * + * @ingroup Media + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License * @see http://exif.org/Exif2-2.PDF The Exif 2.2 specification + * @file */ /** @@ -28,23 +28,21 @@ * @ingroup Media */ class Exif { + + const BYTE = 1; //!< An 8-bit (1-byte) unsigned integer. + const ASCII = 2; //!< An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. + const SHORT = 3; //!< A 16-bit (2-byte) unsigned integer. + const LONG = 4; //!< A 32-bit (4-byte) unsigned integer. + const RATIONAL = 5; //!< Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator + const UNDEFINED = 7; //!< An 8-bit byte that can take any value depending on the field definition + const SLONG = 9; //!< A 32-bit (4-byte) signed integer (2's complement notation), + const SRATIONAL = 10; //!< Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. + //@{ /* @var array * @private */ - /**#@+ - * Exif tag type definition - */ - const BYTE = 1; # An 8-bit (1-byte) unsigned integer. - const ASCII = 2; # An 8-bit byte containing one 7-bit ASCII code. The final byte is terminated with NULL. - const SHORT = 3; # A 16-bit (2-byte) unsigned integer. - const LONG = 4; # A 32-bit (4-byte) unsigned integer. - const RATIONAL = 5; # Two LONGs. The first LONG is the numerator and the second LONG expresses the denominator - const UNDEFINED = 7; # An 8-bit byte that can take any value depending on the field definition - const SLONG = 9; # A 32-bit (4-byte) signed integer (2's complement notation), - const SRATIONAL = 10; # Two SLONGs. The first SLONG is the numerator and the second SLONG is the denominator. - /** * Exif tags grouped by category, the tagname itself is the key and the type * is the value, in the case of more than one possible value type they are diff --git a/includes/Export.php b/includes/Export.php index 5f040b13..909804cf 100644 --- a/includes/Export.php +++ b/includes/Export.php @@ -30,9 +30,10 @@ class WikiExporter { var $dumpUploads = false; - const FULL = 0; - const CURRENT = 1; - const LOGS = 2; + const FULL = 1; + const CURRENT = 2; + const STABLE = 4; // extension defined + const LOGS = 8; const BUFFER = 0; const STREAM = 1; @@ -175,93 +176,103 @@ class WikiExporter { } protected function dumpFrom( $cond = '' ) { - $fname = 'WikiExporter::dumpFrom'; - wfProfileIn( $fname ); - - # For logs dumps... + wfProfileIn( __METHOD__ ); + # For logging dumps... if( $this->history & self::LOGS ) { + if( $this->buffer == WikiExporter::STREAM ) { + $prev = $this->db->bufferResults( false ); + } $where = array( 'user_id = log_user' ); # Hide private logs - $where[] = LogEventsList::getExcludeClause( $this->db ); + $hideLogs = LogEventsList::getExcludeClause( $this->db ); + if( $hideLogs ) $where[] = $hideLogs; + # Add on any caller specified conditions if( $cond ) $where[] = $cond; + # Get logging table name for logging.* clause + $logging = $this->db->tableName('logging'); $result = $this->db->select( array('logging','user'), - '*', + array( "{$logging}.*", 'user_name' ), // grab the user name $where, - $fname, + __METHOD__, array( 'ORDER BY' => 'log_id', 'USE INDEX' => array('logging' => 'PRIMARY') ) ); $wrapper = $this->db->resultObject( $result ); + if( $this->buffer == WikiExporter::STREAM ) { + $this->db->bufferResults( $prev ); + } $this->outputLogStream( $wrapper ); # For page dumps... } else { - list($page,$revision,$text) = $this->db->tableNamesN('page','revision','text'); - - $order = 'ORDER BY page_id'; - $limit = ''; - - if( $this->history == WikiExporter::FULL ) { - $join = 'page_id=rev_page'; - } elseif( $this->history == WikiExporter::CURRENT ) { - if ( $this->list_authors && $cond != '' ) { // List authors, if so desired - $this->do_list_authors ( $page , $revision , $cond ); + $tables = array( 'page', 'revision' ); + $opts = array( 'ORDER BY' => 'page_id ASC' ); + $opts['USE INDEX'] = array(); + $join = array(); + # Full history dumps... + if( $this->history & WikiExporter::FULL ) { + $join['revision'] = array('INNER JOIN','page_id=rev_page'); + # Latest revision dumps... + } elseif( $this->history & WikiExporter::CURRENT ) { + if( $this->list_authors && $cond != '' ) { // List authors, if so desired + list($page,$revision) = $this->db->tableNamesN('page','revision'); + $this->do_list_authors( $page, $revision, $cond ); + } + $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id'); + # "Stable" revision dumps... + } elseif( $this->history & WikiExporter::STABLE ) { + # Default JOIN, to be overridden... + $join['revision'] = array('INNER JOIN','page_id=rev_page AND page_latest=rev_id'); + # One, and only one hook should set this, and return false + if( wfRunHooks( 'WikiExporter::dumpStableQuery', array(&$tables,&$opts,&$join) ) ) { + wfProfileOut( __METHOD__ ); + return new WikiError( __METHOD__." given invalid history dump type." ); } - $join = 'page_id=rev_page AND page_latest=rev_id'; - } elseif ( is_array( $this->history ) ) { - $join = 'page_id=rev_page'; - if ( $this->history['dir'] == 'asc' ) { + # Time offset/limit for all pages/history... + } elseif( is_array( $this->history ) ) { + $revJoin = 'page_id=rev_page'; + # Set time order + if( $this->history['dir'] == 'asc' ) { $op = '>'; - $order .= ', rev_timestamp'; + $opts['ORDER BY'] = 'rev_timestamp ASC'; } else { $op = '<'; - $order .= ', rev_timestamp DESC'; + $opts['ORDER BY'] = 'rev_timestamp DESC'; } - if ( !empty( $this->history['offset'] ) ) { - $join .= " AND rev_timestamp $op " . $this->db->addQuotes( - $this->db->timestamp( $this->history['offset'] ) ); + # Set offset + if( !empty( $this->history['offset'] ) ) { + $revJoin .= " AND rev_timestamp $op " . + $this->db->addQuotes( $this->db->timestamp( $this->history['offset'] ) ); } - if ( !empty( $this->history['limit'] ) ) { - $limitNum = intval( $this->history['limit'] ); - if ( $limitNum > 0 ) { - $limit = "LIMIT $limitNum"; - } + $join['revision'] = array('INNER JOIN',$revJoin); + # Set query limit + if( !empty( $this->history['limit'] ) ) { + $opts['LIMIT'] = intval( $this->history['limit'] ); } + # Uknown history specification parameter? } else { - wfProfileOut( $fname ); - return new WikiError( "$fname given invalid history dump type." ); + wfProfileOut( __METHOD__ ); + return new WikiError( __METHOD__." given invalid history dump type." ); + } + # Query optimization hacks + if( $cond == '' ) { + $opts[] = 'STRAIGHT_JOIN'; + $opts['USE INDEX']['page'] = 'PRIMARY'; + } + # Build text join options + if( $this->text != WikiExporter::STUB ) { // 1-pass + $tables[] = 'text'; + $join['text'] = array('INNER JOIN','rev_text_id=old_id'); } - $where = ( $cond == '' ) ? '' : "$cond AND"; if( $this->buffer == WikiExporter::STREAM ) { $prev = $this->db->bufferResults( false ); } - if( $cond == '' ) { - // Optimization hack for full-database dump - $revindex = $pageindex = $this->db->useIndexClause("PRIMARY"); - $straight = ' /*! STRAIGHT_JOIN */ '; - } else { - $pageindex = ''; - $revindex = ''; - $straight = ''; - } - if( $this->text == WikiExporter::STUB ) { - $sql = "SELECT $straight * FROM - $page $pageindex, - $revision $revindex - WHERE $where $join - $order $limit"; - } else { - $sql = "SELECT $straight * FROM - $page $pageindex, - $revision $revindex, - $text - WHERE $where $join AND rev_text_id=old_id - $order $limit"; - } - $result = $this->db->query( $sql, $fname ); + + # Do the query! + $result = $this->db->select( $tables, '*', $cond, __METHOD__, $opts, $join ); $wrapper = $this->db->resultObject( $result ); + # Output dump results $this->outputPageStream( $wrapper ); - - if ( $this->list_authors ) { + if( $this->list_authors ) { $this->outputPageStream( $wrapper ); } @@ -269,7 +280,7 @@ class WikiExporter { $this->db->bufferResults( $prev ); } } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } /** @@ -399,7 +410,7 @@ class XmlDumpWriter { function namespaces() { global $wgContLang; - $spaces = " <namespaces>\n"; + $spaces = "<namespaces>\n"; foreach( $wgContLang->getFormattedNamespaces() as $ns => $title ) { $spaces .= ' ' . Xml::element( 'namespace', array( 'key' => $ns ), $title ) . "\n"; } diff --git a/includes/ExternalStore.php b/includes/ExternalStore.php index d095aba0..1e750bb5 100644 --- a/includes/ExternalStore.php +++ b/includes/ExternalStore.php @@ -92,6 +92,8 @@ class ExternalStore { $url = $store->store( $params, $data ); // Try to save the object } catch ( DBConnectionError $error ) { $url = false; + } catch( DBQueryError $error ) { + $url = false; } if ( $url ) { return $url; // Done! diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php index 66086b0f..5177d35f 100644 --- a/includes/FileDeleteForm.php +++ b/includes/FileDeleteForm.php @@ -67,7 +67,7 @@ class FileDeleteForm { $reason = $this->DeleteReasonList; if ( $reason != 'other' && $this->DeleteReason != '') { // Entry from drop down menu + additional comment - $reason .= ': ' . $this->DeleteReason; + $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason; } elseif ( $reason == 'other' ) { $reason = $this->DeleteReason; } @@ -108,7 +108,8 @@ class FileDeleteForm { $id = $title->getArticleID( GAID_FOR_UPDATE ); // Need to delete the associated article $article = new Article( $title ); - if( wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason)) ) { + $error = ''; + if( wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason, &$error)) ) { if( $article->doDeleteArticle( $reason, $suppress, $id ) ) { global $wgRequest; if( $wgRequest->getCheck( 'wpWatch' ) ) { diff --git a/includes/ForkController.php b/includes/ForkController.php new file mode 100644 index 00000000..09e1788b --- /dev/null +++ b/includes/ForkController.php @@ -0,0 +1,160 @@ +<?php + +/** + * Class for managing forking command line scripts. + * Currently just does forking and process control, but it could easily be extended + * to provide IPC and job dispatch. + * + * This class requires the posix and pcntl extensions. + */ +class ForkController { + var $children = array(); + var $termReceived = false; + var $flags = 0, $procsToStart = 0; + + static $restartableSignals = array( + SIGFPE, + SIGILL, + SIGSEGV, + SIGBUS, + SIGABRT, + SIGSYS, + SIGPIPE, + SIGXCPU, + SIGXFSZ, + ); + + /** + * Pass this flag to __construct() to cause the class to automatically restart + * workers that exit with non-zero exit status or a signal such as SIGSEGV. + */ + const RESTART_ON_ERROR = 1; + + public function __construct( $numProcs, $flags = 0 ) { + if ( php_sapi_name() != 'cli' ) { + throw new MWException( "MultiProcess cannot be used from the web." ); + } + $this->procsToStart = $numProcs; + $this->flags = $flags; + } + + /** + * Start the child processes. + * + * This should only be called from the command line. It should be called + * as early as possible during execution. + * + * This will return 'child' in the child processes. In the parent process, + * it will run until all the child processes exit or a TERM signal is + * received. It will then return 'done'. + */ + public function start() { + // Trap SIGTERM + pcntl_signal( SIGTERM, array( $this, 'handleTermSignal' ), false ); + + do { + // Start child processes + if ( $this->procsToStart ) { + if ( $this->forkWorkers( $this->procsToStart ) == 'child' ) { + return 'child'; + } + $this->procsToStart = 0; + } + + // Check child status + $status = false; + $deadPid = pcntl_wait( $status ); + + if ( $deadPid > 0 ) { + // Respond to child process termination + unset( $this->children[$deadPid] ); + if ( $this->flags & self::RESTART_ON_ERROR ) { + if ( pcntl_wifsignaled( $status ) ) { + // Restart if the signal was abnormal termination + // Don't restart if it was deliberately killed + $signal = pcntl_wtermsig( $status ); + if ( in_array( $signal, self::$restartableSignals ) ) { + echo "Worker exited with signal $signal, restarting\n"; + $this->procsToStart++; + } + } elseif ( pcntl_wifexited( $status ) ) { + // Restart on non-zero exit status + $exitStatus = pcntl_wexitstatus( $status ); + if ( $exitStatus > 0 ) { + echo "Worker exited with status $exitStatus, restarting\n"; + $this->procsToStart++; + } + } + } + // Throttle restarts + if ( $this->procsToStart ) { + usleep( 500000 ); + } + } + + // Run signal handlers + if ( function_exists( 'pcntl_signal_dispatch' ) ) { + pcntl_signal_dispatch(); + } else { + declare (ticks=1) { $status = $status; } + } + // Respond to TERM signal + if ( $this->termReceived ) { + foreach ( $this->children as $childPid => $unused ) { + posix_kill( $childPid, SIGTERM ); + } + $this->termReceived = false; + } + } while ( count( $this->children ) ); + pcntl_signal( SIGTERM, SIG_DFL ); + return 'done'; + } + + protected function prepareEnvironment() { + global $wgCaches, $wgMemc; + // Don't share DB or memcached connections + wfGetLBFactory()->destroyInstance(); + $wgCaches = array(); + unset( $wgMemc ); + } + + /** + * Fork a number of worker processes. + */ + protected function forkWorkers( $numProcs ) { + global $wgMemc, $wgCaches, $wgMainCacheType; + + $this->prepareEnvironment(); + + // Create the child processes + for ( $i = 0; $i < $numProcs; $i++ ) { + // Do the fork + $pid = pcntl_fork(); + if ( $pid === -1 || $pid === false ) { + echo "Error creating child processes\n"; + exit( 1 ); + } + + if ( !$pid ) { + $this->initChild(); + return 'child'; + } else { + // This is the parent process + $this->children[$pid] = true; + } + } + + return 'parent'; + } + + protected function initChild() { + global $wgMemc, $wgMainCacheType; + $wgMemc = wfGetCache( $wgMainCacheType ); + $this->children = null; + pcntl_signal( SIGTERM, SIG_DFL ); + } + + protected function handleTermSignal( $signal ) { + $this->termReceived = true; + } +} diff --git a/includes/GlobalFunctions.php b/includes/GlobalFunctions.php index 33f5831d..0807f0be 100644 --- a/includes/GlobalFunctions.php +++ b/includes/GlobalFunctions.php @@ -89,6 +89,19 @@ if ( !function_exists( 'array_diff_key' ) ) { } } +// Support for Wietse Venema's taint feature +if ( !function_exists( 'istainted' ) ) { + function istainted( $var ) { + return 0; + } + function taint( $var, $level = 0 ) {} + function untaint( $var, $level = 0 ) {} + define( 'TC_HTML', 1 ); + define( 'TC_SHELL', 1 ); + define( 'TC_MYSQL', 1 ); + define( 'TC_PCRE', 1 ); + define( 'TC_SELF', 1 ); +} /// @endcond @@ -337,12 +350,14 @@ function wfErrorLog( $text, $file ) { */ function wfLogProfilingData() { global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest; - global $wgProfiler, $wgUser; - if ( !isset( $wgProfiler ) ) - return; - + global $wgProfiler, $wgProfileLimit, $wgUser; + # Profiling must actually be enabled... + if( !isset( $wgProfiler ) ) return; + # Get total page request time $now = wfTime(); $elapsed = $now - $wgRequestTime; + # Only show pages that longer than $wgProfileLimit time (default is 0) + if( $elapsed <= $wgProfileLimit ) return; $prof = wfGetProfilingOutput( $wgRequestTime, $elapsed ); $forward = ''; if( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) @@ -431,7 +446,7 @@ function wfGetLangObj( $langcode = false ){ return Language::factory( $langcode ); # $langcode is a string, but not a valid language code; use content language. - wfDebug( 'Invalid language code passed to wfGetLangObj, falling back to content language.' ); + wfDebug( "Invalid language code passed to wfGetLangObj, falling back to content language.\n" ); return $wgContLang; } @@ -771,7 +786,7 @@ function wfAbruptExit( $error = false ){ wfDebug("WARNING: Abrupt exit in $file at line $line\n"); } } else { - wfDebug('WARNING: Abrupt exit\n'); + wfDebug("WARNING: Abrupt exit\n"); } wfLogProfilingData(); @@ -860,18 +875,35 @@ function wfHostname() { * murky circumstances, which may be triggered in part by stub objects * or other fancy talkin'. * - * Will return an empty array if Zend Optimizer is detected, otherwise - * the output from debug_backtrace() (trimmed). + * Will return an empty array if Zend Optimizer is detected or if + * debug_backtrace is disabled, otherwise the output from + * debug_backtrace() (trimmed). * * @return array of backtrace information */ function wfDebugBacktrace() { + static $disabled = null; + if( extension_loaded( 'Zend Optimizer' ) ) { wfDebug( "Zend Optimizer detected; skipping debug_backtrace for safety.\n" ); return array(); - } else { - return array_slice( debug_backtrace(), 1 ); } + + if ( is_null( $disabled ) ) { + $disabled = false; + $functions = explode( ',', ini_get( 'disable_functions' ) ); + $functions = array_map( 'trim', $functions ); + $functions = array_map( 'strtolower', $functions ); + if ( in_array( 'debug_backtrace', $functions ) ) { + wfDebug( "debug_backtrace is in disabled_functions\n" ); + $disabled = true; + } + } + if ( $disabled ) { + return array(); + } + + return array_slice( debug_backtrace(), 1 ); } function wfBacktrace() { @@ -927,7 +959,8 @@ function wfBacktrace() { */ function wfShowingResults( $offset, $limit ) { global $wgLang; - return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ) ); + return wfMsgExt( 'showingresults', array( 'parseinline' ), $wgLang->formatNum( $limit ), + $wgLang->formatNum( $offset+1 ) ); } /** @@ -935,18 +968,28 @@ function wfShowingResults( $offset, $limit ) { */ function wfShowingResultsNum( $offset, $limit, $num ) { global $wgLang; - return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) ); + return wfMsgExt( 'showingresultsnum', array( 'parseinline' ), $wgLang->formatNum( $limit ), + $wgLang->formatNum( $offset+1 ), $wgLang->formatNum( $num ) ); } /** - * @todo document + * Generate (prev x| next x) (20|50|100...) type links for paging + * @param $offset string + * @param $limit int + * @param $link string + * @param $query string, optional URL query parameter string + * @param $atend bool, optional param for specified if this is the last page */ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) { global $wgLang; $fmtLimit = $wgLang->formatNum( $limit ); - $prev = wfMsg( 'prevn', $fmtLimit ); - $next = wfMsg( 'nextn', $fmtLimit ); - + # Get prev/next link display text + $prev = wfMsgHtml( 'prevn', $fmtLimit ); + $next = wfMsgHtml( 'nextn', $fmtLimit ); + # Get prev/next link title text + $pTitle = wfMsgExt( 'prevn-title', array('parsemag','escape'), $fmtLimit ); + $nTitle = wfMsgExt( 'nextn-title', array('parsemag','escape'), $fmtLimit ); + # Fetch the title object if( is_object( $link ) ) { $title =& $link; } else { @@ -955,44 +998,58 @@ function wfViewPrevNext( $offset, $limit, $link, $query = '', $atend = false ) { return false; } } - - if ( 0 != $offset ) { + # Make 'previous' link + if( 0 != $offset ) { $po = $offset - $limit; - if ( $po < 0 ) { $po = 0; } + $po = max($po,0); $q = "limit={$limit}&offset={$po}"; - if ( '' != $query ) { $q .= '&'.$query; } - $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-prevlink\">{$prev}</a>"; - } else { $plink = $prev; } - + if( $query != '' ) { + $q .= '&'.$query; + } + $plink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$pTitle}\" class=\"mw-prevlink\">{$prev}</a>"; + } else { + $plink = $prev; + } + # Make 'next' link $no = $offset + $limit; - $q = 'limit='.$limit.'&offset='.$no; - if ( '' != $query ) { $q .= '&'.$query; } - - if ( $atend ) { + $q = "limit={$limit}&offset={$no}"; + if( $query != '' ) { + $q .= '&'.$query; + } + if( $atend ) { $nlink = $next; } else { - $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-nextlink\">{$next}</a>"; - } - $nums = wfNumLink( $offset, 20, $title, $query ) . ' | ' . - wfNumLink( $offset, 50, $title, $query ) . ' | ' . - wfNumLink( $offset, 100, $title, $query ) . ' | ' . - wfNumLink( $offset, 250, $title, $query ) . ' | ' . - wfNumLink( $offset, 500, $title, $query ); - + $nlink = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$nTitle}\" class=\"mw-nextlink\">{$next}</a>"; + } + # Make links to set number of items per page + $nums = $wgLang->pipeList( array( + wfNumLink( $offset, 20, $title, $query ), + wfNumLink( $offset, 50, $title, $query ), + wfNumLink( $offset, 100, $title, $query ), + wfNumLink( $offset, 250, $title, $query ), + wfNumLink( $offset, 500, $title, $query ) + ) ); return wfMsg( 'viewprevnext', $plink, $nlink, $nums ); } /** - * @todo document + * Generate links for (20|50|100...) items-per-page links + * @param $offset string + * @param $limit int + * @param $title Title + * @param $query string, optional URL query parameter string */ -function wfNumLink( $offset, $limit, &$title, $query = '' ) { +function wfNumLink( $offset, $limit, $title, $query = '' ) { global $wgLang; - if ( '' == $query ) { $q = ''; } - else { $q = $query.'&'; } - $q .= 'limit='.$limit.'&offset='.$offset; - + if( $query == '' ) { + $q = ''; + } else { + $q = $query.'&'; + } + $q .= "limit={$limit}&offset={$offset}"; $fmtLimit = $wgLang->formatNum( $limit ); - $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" class=\"mw-numlink\">{$fmtLimit}</a>"; + $lTitle = wfMsgExt('shown-title',array('parsemag','escape'),$limit); + $s = '<a href="' . $title->escapeLocalUrl( $q ) . "\" title=\"{$lTitle}\" class=\"mw-numlink\">{$fmtLimit}</a>"; return $s; } @@ -1693,6 +1750,11 @@ define('TS_ORACLE', 6); define('TS_POSTGRES', 7); /** + * DB2 format time + */ +define('TS_DB2', 8); + +/** * @param mixed $outputtype A timestamp in one of the supported formats, the * function will autodetect which format is supplied * and act accordingly. @@ -1753,6 +1815,8 @@ function wfTimestamp($outputtype=TS_UNIX,$ts=0) { return gmdate( 'd-M-y h.i.s A', $uts) . ' +00:00'; case TS_POSTGRES: return gmdate( 'Y-m-d H:i:s', $uts) . ' GMT'; + case TS_DB2: + return gmdate( 'Y-m-d H:i:s', $uts); default: throw new MWException( 'wfTimestamp() called with illegal output type.'); } @@ -1837,7 +1901,7 @@ function wfGetCachedNotice( $name ) { $parserMemc->set( $key, array( 'html' => $parsed, 'hash' => md5( $notice ) ), 600 ); $notice = $parsed; } else { - wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available' ); + wfDebug( 'wfGetCachedNotice called for ' . $name . ' with no $wgOut available'."\n" ); $notice = ''; } } @@ -1929,11 +1993,16 @@ function wfTempDir() { * * @param string $dir Full path to directory to create * @param int $mode Chmod value to use, default is $wgDirectoryMode + * @param string $caller Optional caller param for debugging. * @return bool */ -function wfMkdirParents( $dir, $mode = null ) { +function wfMkdirParents( $dir, $mode = null, $caller = null ) { global $wgDirectoryMode; + if ( !is_null( $caller ) ) { + wfDebug( "$caller: called wfMkdirParents($dir)" ); + } + if( strval( $dir ) === '' || file_exists( $dir ) ) return true; @@ -2101,11 +2170,26 @@ function wfIniGetBool( $setting ) { function wfShellExec( $cmd, &$retval=null ) { global $IP, $wgMaxShellMemory, $wgMaxShellFileSize, $wgMaxShellTime; - if( wfIniGetBool( 'safe_mode' ) ) { - wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" ); + static $disabled; + if ( is_null( $disabled ) ) { + $disabled = false; + if( wfIniGetBool( 'safe_mode' ) ) { + wfDebug( "wfShellExec can't run in safe_mode, PHP's exec functions are too broken.\n" ); + $disabled = true; + } + $functions = explode( ',', ini_get( 'disable_functions' ) ); + $functions = array_map( 'trim', $functions ); + $functions = array_map( 'strtolower', $functions ); + if ( in_array( 'passthru', $functions ) ) { + wfDebug( "passthru is in disabled_functions\n" ); + $disabled = true; + } + } + if ( $disabled ) { $retval = 1; return "Unable to run external programs in safe mode."; } + wfInitShellLocale(); if ( php_uname( 's' ) == 'Linux' ) { @@ -2317,9 +2401,16 @@ function wfMergeErrorArrays(/*...*/) { } /** - * Make a URL index, appropriate for the el_index field of externallinks. + * parse_url() work-alike, but non-broken. Differences: + * + * 1) Does not raise warnings on bad URLs (just returns false) + * 2) Handles protocols that don't use :// (e.g., mailto: and news:) correctly + * 3) Adds a "delimiter" element to the array, either '://' or ':' (see (2)) + * + * @param string $url A URL to parse + * @return array Bits of the URL in an associative array, per PHP docs */ -function wfMakeUrlIndex( $url ) { +function wfParseUrl( $url ) { global $wgUrlProtocols; // Allow all protocols defined in DefaultSettings/LocalSettings.php wfSuppressWarnings(); $bits = parse_url( $url ); @@ -2327,12 +2418,12 @@ function wfMakeUrlIndex( $url ) { if ( !$bits ) { return false; } + // most of the protocols are followed by ://, but mailto: and sometimes news: not, check for it - $delimiter = ''; - if ( in_array( $bits['scheme'] . '://' , $wgUrlProtocols ) ) { - $delimiter = '://'; - } elseif ( in_array( $bits['scheme'] .':' , $wgUrlProtocols ) ) { - $delimiter = ':'; + if ( in_array( $bits['scheme'] . '://', $wgUrlProtocols ) ) { + $bits['delimiter'] = '://'; + } elseif ( in_array( $bits['scheme'] . ':', $wgUrlProtocols ) ) { + $bits['delimiter'] = ':'; // parse_url detects for news: and mailto: the host part of an url as path // We have to correct this wrong detection if ( isset ( $bits['path'] ) ) { @@ -2343,6 +2434,15 @@ function wfMakeUrlIndex( $url ) { return false; } + return $bits; +} + +/** + * Make a URL index, appropriate for the el_index field of externallinks. + */ +function wfMakeUrlIndex( $url ) { + $bits = wfParseUrl( $url ); + // Reverse the labels in the hostname, convert to lower case // For emails reverse domainpart only if ( $bits['scheme'] == 'mailto' ) { @@ -2364,7 +2464,7 @@ function wfMakeUrlIndex( $url ) { } // Reconstruct the pseudo-URL $prot = $bits['scheme']; - $index = "$prot$delimiter$reversedHost"; + $index = $prot . $bits['delimiter'] . $reversedHost; // Leave out user and password. Add the port, path, query and fragment if ( isset( $bits['port'] ) ) $index .= ':' . $bits['port']; if ( isset( $bits['path'] ) ) { diff --git a/includes/HTMLCacheUpdate.php b/includes/HTMLCacheUpdate.php index 402102ea..bd63c072 100644 --- a/includes/HTMLCacheUpdate.php +++ b/includes/HTMLCacheUpdate.php @@ -35,146 +35,76 @@ class HTMLCacheUpdate $this->mTable = $table; $this->mRowsPerJob = $wgUpdateRowsPerJob; $this->mRowsPerQuery = $wgUpdateRowsPerQuery; + $this->mCache = $this->mTitle->getBacklinkCache(); } public function doUpdate() { # Fetch the IDs - $cond = $this->getToCondition(); - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( $this->mTable, $this->getFromField(), $cond, __METHOD__ ); + $numRows = $this->mCache->getNumLinks( $this->mTable ); - if ( $dbr->numRows( $res ) != 0 ) { - if ( $dbr->numRows( $res ) > $this->mRowsPerJob ) { - $this->insertJobs( $res ); + if ( $numRows != 0 ) { + if ( $numRows > $this->mRowsPerJob ) { + $this->insertJobs(); } else { - $this->invalidateIDs( $res ); + $this->invalidate(); } } wfRunHooks( 'HTMLCacheUpdate::doUpdate', array($this->mTitle) ); } - protected function insertJobs( ResultWrapper $res ) { - $numRows = $res->numRows(); - $numBatches = ceil( $numRows / $this->mRowsPerJob ); - $realBatchSize = $numRows / $numBatches; - $start = false; - $jobs = array(); - do { - for ( $i = 0; $i <= $realBatchSize - 1; $i++ ) { - $row = $res->fetchRow(); - if ( $row ) { - $id = $row[0]; - } else { - $id = false; - break; - } - } - + protected function insertJobs() { + $batches = $this->mCache->partition( $this->mTable, $this->mRowsPerJob ); + if ( !$batches ) { + return; + } + foreach ( $batches as $batch ) { $params = array( 'table' => $this->mTable, - 'start' => $start, - 'end' => ( $id !== false ? $id - 1 : false ), + 'start' => $batch[0], + 'end' => $batch[1], ); $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params ); - - $start = $id; - } while ( $start ); - - Job::batchInsert( $jobs ); - } - - protected function getPrefix() { - static $prefixes = array( - 'pagelinks' => 'pl', - 'imagelinks' => 'il', - 'categorylinks' => 'cl', - 'templatelinks' => 'tl', - 'redirect' => 'rd', - ); - - if ( is_null( $this->mPrefix ) ) { - $this->mPrefix = $prefixes[$this->mTable]; - if ( is_null( $this->mPrefix ) ) { - throw new MWException( "Invalid table type \"{$this->mTable}\" in " . __CLASS__ ); - } } - return $this->mPrefix; - } - - public function getFromField() { - return $this->getPrefix() . '_from'; + Job::batchInsert( $jobs ); } - public function getToCondition() { - $prefix = $this->getPrefix(); - switch ( $this->mTable ) { - case 'pagelinks': - case 'templatelinks': - case 'redirect': - return array( - "{$prefix}_namespace" => $this->mTitle->getNamespace(), - "{$prefix}_title" => $this->mTitle->getDBkey() - ); - case 'imagelinks': - return array( 'il_to' => $this->mTitle->getDBkey() ); - case 'categorylinks': - return array( 'cl_to' => $this->mTitle->getDBkey() ); - } - throw new MWException( 'Invalid table type in ' . __CLASS__ ); - } /** - * Invalidate a set of IDs, right now + * Invalidate a set of pages, right now */ - public function invalidateIDs( ResultWrapper $res ) { + public function invalidate( $startId = false, $endId = false ) { global $wgUseFileCache, $wgUseSquid; - if ( $res->numRows() == 0 ) { + $titleArray = $this->mCache->getLinks( $this->mTable, $startId, $endId ); + if ( $titleArray->count() == 0 ) { return; } $dbw = wfGetDB( DB_MASTER ); $timestamp = $dbw->timestamp(); - $done = false; - - while ( !$done ) { - # Get all IDs in this query into an array - $ids = array(); - for ( $i = 0; $i < $this->mRowsPerQuery; $i++ ) { - $row = $res->fetchRow(); - if ( $row ) { - $ids[] = $row[0]; - } else { - $done = true; - break; - } - } - if ( !count( $ids ) ) { - break; - } + # Get all IDs in this query into an array + $ids = array(); + foreach ( $titleArray as $title ) { + $ids[] = $title->getArticleID(); + } + # Update page_touched + $dbw->update( 'page', + array( 'page_touched' => $timestamp ), + array( 'page_id IN (' . $dbw->makeList( $ids ) . ')' ), + __METHOD__ + ); - # Update page_touched - $dbw->update( 'page', - array( 'page_touched' => $timestamp ), - array( 'page_id IN (' . $dbw->makeList( $ids ) . ')' ), - __METHOD__ - ); + # Update squid + if ( $wgUseSquid ) { + $u = SquidUpdate::newFromTitles( $titleArray ); + $u->doUpdate(); + } - # Update squid - if ( $wgUseSquid || $wgUseFileCache ) { - $titles = Title::newFromIDs( $ids ); - if ( $wgUseSquid ) { - $u = SquidUpdate::newFromTitles( $titles ); - $u->doUpdate(); - } - - # Update file cache - if ( $wgUseFileCache ) { - foreach ( $titles as $title ) { - HTMLFileCache::clearFileCache( $title ); - } - } + # Update file cache + if ( $wgUseFileCache ) { + foreach ( $titleArray as $title ) { + HTMLFileCache::clearFileCache( $title ); } } } @@ -204,20 +134,7 @@ class HTMLCacheUpdateJob extends Job { public function run() { $update = new HTMLCacheUpdate( $this->title, $this->table ); - - $fromField = $update->getFromField(); - $conds = $update->getToCondition(); - if ( $this->start ) { - $conds[] = "$fromField >= {$this->start}"; - } - if ( $this->end ) { - $conds[] = "$fromField <= {$this->end}"; - } - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( $this->table, $fromField, $conds, __METHOD__ ); - $update->invalidateIDs( $res ); - + $update->invalidate( $this->start, $this->end ); return true; } } diff --git a/includes/HTMLFileCache.php b/includes/HTMLFileCache.php index e267962c..68cafa24 100644 --- a/includes/HTMLFileCache.php +++ b/includes/HTMLFileCache.php @@ -128,7 +128,6 @@ class HTMLFileCache { public function loadFromFileCache() { global $wgOut, $wgMimeType, $wgOutputEncoding, $wgContLanguageCode; wfDebug(" loadFromFileCache()\n"); - $filename = $this->fileCacheName(); // Raw pages should handle cache control on their own, // even when using file cache. This reduces hits from clients. @@ -148,6 +147,7 @@ class HTMLFileCache { } } readfile( $filename ); + $wgOut->disable(); // tell $wgOut that output is taken care of } protected function checkCacheDirs() { @@ -159,13 +159,12 @@ class HTMLFileCache { wfMkdirParents( $mydir2 ); } - public function saveToFileCache( $origtext ) { + public function saveToFileCache( $text ) { global $wgUseFileCache; - if( !$wgUseFileCache ) { - return $origtext; // return to output + if( !$wgUseFileCache || strlen( $text ) < 512 ) { + // Disabled or empty/broken output (OOM and PHP errors) + return $text; } - $text = $origtext; - if( strcmp($text,'') == 0 ) return ''; wfDebug(" saveToFileCache()\n", false); diff --git a/includes/ImageGallery.php b/includes/ImageGallery.php index f3f525c1..8a38bed7 100644 --- a/includes/ImageGallery.php +++ b/includes/ImageGallery.php @@ -289,7 +289,7 @@ class ImageGallery } $textlink = $this->mShowFilename ? - $sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20, '...' ) ) ) . "<br />\n" : + $sk->makeKnownLinkObj( $nt, htmlspecialchars( $wgLang->truncate( $nt->getText(), 20 ) ) ) . "<br />\n" : '' ; # ATTENTION: The newline after <div class="gallerytext"> is needed to accommodate htmltidy which diff --git a/includes/ImagePage.php b/includes/ImagePage.php index 314d478e..4f3b859a 100644 --- a/includes/ImagePage.php +++ b/includes/ImagePage.php @@ -105,7 +105,6 @@ class ImagePage extends Article { } else { # Just need to set the right headers $wgOut->setArticleFlag( true ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); $this->viewUpdates(); } @@ -117,8 +116,6 @@ class ImagePage extends Article { $wgOut->addWikiText( $fol ); } $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' ); - } else { - $this->checkSharedConflict(); } $this->closeShowImage(); @@ -129,11 +126,9 @@ class ImagePage extends Article { array( 'id' => 'filelinks' ), wfMsg( 'imagelinks' ) ) . "\n" ); $this->imageDupes(); - // TODO: We may want to find local images redirecting to a foreign - // file: "The following local files redirect to this file" - if( $this->img->isLocal() ) { - $this->imageRedirects(); - } + # TODO! FIXME! For some freaky reason, we can't redirect to foreign images. + # Yet we return metadata about the target. Definitely an issue in the FileRepo + $this->imageRedirects(); $this->imageLinks(); if( $showmeta ) { @@ -473,6 +468,7 @@ EOT $title = SpecialPage::getTitleFor( 'Upload' ); $link = $sk->makeKnownLinkObj($title, wfMsgHtml('noimage-linktext'), 'wpDestFile=' . urlencode( $this->displayImg->getName() ) ); + $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->addHTML( wfMsgWikiHtml( 'noimage', $link ) ); } } @@ -487,17 +483,18 @@ EOT $descUrl = $this->img->getDescriptionUrl(); $descText = $this->img->getDescriptionText(); - $s = "<div class='sharedUploadNotice'>" . wfMsgWikiHtml( 'sharedupload' ); + $msg = ''; if( $descUrl ) { $sk = $wgUser->getSkin(); $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadwiki-linktext' ) ); $msg = ( $descText ) ? 'shareduploadwiki-desc' : 'shareduploadwiki'; $msg = wfMsgExt( $msg, array( 'parseinline', 'replaceafter' ), $link ); - if( $msg != '-' ) { - # Show message only if not voided by local sysops - $s .= $msg; + if( $msg == '-' ) { + $msg = ''; } } + $s = "<div class='sharedUploadNotice'>"; + $s .= wfMsgWikiHtml( 'sharedupload', $this->img->getRepo()->getDisplayName(), $msg ); $s .= "</div>"; $wgOut->addHTML( $s ); @@ -506,58 +503,10 @@ EOT } } - /* - * Check for files with the same name on the foreign repos. - */ - protected function checkSharedConflict() { - global $wgOut, $wgUser; - - $repoGroup = RepoGroup::singleton(); - if( !$repoGroup->hasForeignRepos() ) { - return; - } - - $this->loadFile(); - if( !$this->img->isLocal() ) { - return; - } - - $this->dupFile = null; - $repoGroup->forEachForeignRepo( array( $this, 'checkSharedConflictCallback' ) ); - - if( !$this->dupFile ) - return; - $dupfile = $this->dupFile; - $same = ( - ($this->img->getSha1() == $dupfile->getSha1()) && - ($this->img->getSize() == $dupfile->getSize()) - ); - - $sk = $wgUser->getSkin(); - $descUrl = $dupfile->getDescriptionUrl(); - if( $same ) { - $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadduplicate-linktext' ) ); - $wgOut->addHTML( '<div id="shared-image-dup">' . wfMsgWikiHtml( 'shareduploadduplicate', $link ) . '</div>' ); - } else { - $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadconflict-linktext' ) ); - $wgOut->addHTML( '<div id="shared-image-conflict">' . wfMsgWikiHtml( 'shareduploadconflict', $link ) . '</div>' ); - } - } - - public function checkSharedConflictCallback( $repo ) { - $this->loadFile(); - $dupfile = $repo->newFile( $this->img->getTitle() ); - if( $dupfile && $dupfile->exists() ) { - $this->dupFile = $dupfile; - return $dupfile->exists(); - } - return false; - } - public function getUploadUrl() { $this->loadFile(); $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); - return $uploadTitle->getFullUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) ); + return $uploadTitle->getFullUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) . '&wpForReUpload=1' ); } /** @@ -581,10 +530,6 @@ EOT $wgOut->addHTML( "<li><div class='plainlinks'>{$ulink}</div></li>" ); } - # Link to Special:FileDuplicateSearch - $dupeLink = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'FileDuplicateSearch', $this->mTitle->getDBkey() ), wfMsgHtml( 'imagepage-searchdupe' ) ); - $wgOut->addHTML( "<li>{$dupeLink}</li>" ); - # External editing link $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' ); $wgOut->addHTML( '<li>' . $elink . ' <small>' . wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . '</small></li>' ); @@ -698,19 +643,21 @@ EOT $wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" ); $wgOut->addWikiMsg( 'duplicatesoffile', - $wgLang->formatNum( count( $dupes ) ) + $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey() ); $wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" ); $sk = $wgUser->getSkin(); foreach ( $dupes as $file ) { + $fromSrc = ''; if( $file->isLocal() ) $link = $sk->makeKnownLinkObj( $file->getTitle(), "" ); else { $link = $sk->makeExternalLink( $file->getDescriptionUrl(), $file->getTitle()->getPrefixedText() ); + $fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() ); } - $wgOut->addHTML( "<li>{$link}</li>\n" ); + $wgOut->addHTML( "<li>{$link} {$fromSrc}</li>\n" ); } $wgOut->addHTML( "</ul></div>\n" ); } @@ -922,7 +869,8 @@ class ImageHistoryList { 'alt' => wfMsg( 'filehist-thumbtext', $wgLang->timeAndDate( $timestamp, true ) ), 'file-link' => true, ); - $row .= '</td><td>' . $thumbnail->toHtml( $options ); + $row .= '</td><td>' . ( $thumbnail ? $thumbnail->toHtml( $options ) : + wfMsgHtml( 'filehist-nothumb' ) ); } else { $row .= '</td><td>' . wfMsgHtml( 'filehist-nothumb' ); } diff --git a/includes/Import.php b/includes/Import.php index 56e7a7fb..973866df 100644 --- a/includes/Import.php +++ b/includes/Import.php @@ -223,7 +223,7 @@ class WikiRevision { } elseif( $changed ) { wfDebug( __METHOD__ . ": running onArticleEdit\n" ); - Article::onArticleEdit( $this->title, 'skiptransclusions' ); // leave templatelinks for editUpdates() + Article::onArticleEdit( $this->title ); wfDebug( __METHOD__ . ": running edit updates\n" ); $article->editUpdates( @@ -1116,7 +1116,7 @@ class ImportStreamSource { } } - public static function newFromInterwiki( $interwiki, $page, $history=false ) { + public static function newFromInterwiki( $interwiki, $page, $history = false, $templates = false, $pageLinkDepth = 0 ) { if( $page == '' ) { return new WikiErrorMsg( 'import-noarticle' ); } @@ -1124,7 +1124,10 @@ class ImportStreamSource { if( is_null( $link ) || $link->getInterwiki() == '' ) { return new WikiErrorMsg( 'importbadinterwiki' ); } else { - $params = $history ? 'history=1' : ''; + $params = array(); + if ( $history ) $params['history'] = 1; + if ( $templates ) $params['templates'] = 1; + if ( $pageLinkDepth ) $params['pagelink-depth'] = $pageLinkDepth; $url = $link->getFullUrl( $params ); # For interwikis, use POST to avoid redirects. return ImportStreamSource::newFromURL( $url, "POST" ); diff --git a/includes/Linker.php b/includes/Linker.php index f116fb4a..b739244b 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -618,7 +618,7 @@ class Linker { $img = ''; $success = wfRunHooks('LinkerMakeExternalImage', array( &$url, &$alt, &$img ) ); if(!$success) { - wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}", true); + wfDebug("Hook LinkerMakeExternalImage changed the output of external image with url {$url} and alt text {$alt} to {$img}\n", true); return $img; } return Xml::element( 'img', @@ -882,10 +882,13 @@ class Linker { } } - if( $page ) { - $query = $query ? '&page=' . urlencode( $page ) : 'page=' . urlencode( $page ); - } + # ThumbnailImage::toHtml() already adds page= onto the end of DjVu URLs + # So we don't need to pass it here in $query. However, the URL for the + # zoom icon still needs it, so we make a unique query for it. See bug 14771 $url = $title->getLocalURL( $query ); + if( $page ) { + $url = wfAppendQuery( $url, 'page=' . urlencode( $page ) ); + } $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) ); @@ -1007,22 +1010,37 @@ class Linker { wfMsg( $key ) ); } - /** @todo document */ + /** + * Make an external link + * @param String $url URL to link to + * @param String $text text of link + * @param boolean $escape Do we escape the link text? + * @param String $linktype Type of external link. Gets added to the classes + * @param array $attribs Array of extra attributes to <a> + * + * @TODO! @FIXME! This is a really crappy implementation. $linktype and + * 'external' are mashed into the class attrib for the link (which is made + * into a string). Then, if we've got additional params in $attribs, we + * add to it. People using this might want to change the classes (or other + * default link attributes), but passing $attribsText is just messy. Would + * make a lot more sense to make put the classes into $attribs, let the + * hook play with them, *then* expand it all at once. + */ function makeExternalLink( $url, $text, $escape = true, $linktype = '', $attribs = array() ) { $attribsText = $this->getExternalLinkAttributes( $url, $text, 'external ' . $linktype ); - if ( $attribs ) { - $attribsText .= Xml::expandAttributes( $attribs ); - } $url = htmlspecialchars( $url ); if( $escape ) { $text = htmlspecialchars( $text ); } $link = ''; - $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link ) ); + $success = wfRunHooks('LinkerMakeExternalLink', array( &$url, &$text, &$link, &$attribs, $linktype ) ); if(!$success) { - wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}", true); + wfDebug("Hook LinkerMakeExternalLink changed the output of link with url {$url} and text {$text} to {$link}\n", true); return $link; } + if ( $attribs ) { + $attribsText .= Xml::expandAttributes( $attribs ); + } return '<a href="'.$url.'"'.$attribsText.'>'.$text.'</a>'; } @@ -1053,7 +1071,7 @@ class Linker { * @return string */ public function userToolLinks( $userId, $userText, $redContribsWhenNoEdits = false, $flags = 0, $edits=null ) { - global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans; + global $wgUser, $wgDisableAnonTalk, $wgSysopUserBans, $wgLang; $talkable = !( $wgDisableAnonTalk && 0 == $userId ); $blockable = ( $wgSysopUserBans || 0 == $userId ) && !$flags & self::TOOL_LINKS_NOBLOCK; @@ -1079,7 +1097,7 @@ class Linker { } if( $items ) { - return ' <span class="mw-usertoollinks">(' . implode( ' | ', $items ) . ')</span>'; + return ' <span class="mw-usertoollinks">(' . $wgLang->pipeList( $items ) . ')</span>'; } else { return ''; } @@ -1783,9 +1801,7 @@ class Linker { # FIXME: Per standard MW behavior, a value of '-' means to suppress the # attribute, but this is broken for accesskey: that might be a useful # value. - if( $accesskey != '' - && $accesskey != '-' - && !wfEmptyMsg( "accesskey-$name", $accesskey ) ) { + if( $accesskey != '' && $accesskey != '-' && !wfEmptyMsg( "accesskey-$name", $accesskey ) ) { wfProfileOut( __METHOD__ ); return $accesskey; } @@ -1793,4 +1809,21 @@ class Linker { wfProfileOut( __METHOD__ ); return false; } + + /** + * Creates a (show/hide) link for deleting revisions/log entries + * + * @param array $query Query parameters to be passed to link() + * @param bool $restricted Set to true to use a <strong> instead of a <span> + * + * @return string HTML <a> link to Special:Revisiondelete, wrapped in a + * span to allow for customization of appearance with CSS + */ + public function revDeleteLink( $query = array(), $restricted = false ) { + $sp = SpecialPage::getTitleFor( 'Revisiondelete' ); + $text = wfMsgHtml( 'rev-delundel' ); + $tag = $restricted ? 'strong' : 'span'; + $link = $this->link( $sp, $text, array(), $query, array( 'known', 'noclasses' ) ); + return Xml::tags( $tag, array( 'class' => 'mw-revdelundel-link' ), "($link)" ); + } } diff --git a/includes/LinksUpdate.php b/includes/LinksUpdate.php index 13f35b5a..caacb49c 100644 --- a/includes/LinksUpdate.php +++ b/includes/LinksUpdate.php @@ -20,8 +20,7 @@ class LinksUpdate { $mProperties, //!< Map of arbitrary name to value $mDb, //!< Database connection reference $mOptions, //!< SELECT options to be used (array) - $mRecursive, //!< Whether to queue jobs for recursive updates - $mTouchTmplLinks; //!< Whether to queue HTMLCacheUpdate jobs IF recursive + $mRecursive; //!< Whether to queue jobs for recursive updates /**@}}*/ /** @@ -72,15 +71,6 @@ class LinksUpdate { wfRunHooks( 'LinksUpdateConstructed', array( &$this ) ); } - - /** - * Invalidate HTML cache of pages that include this page? - */ - public function setRecursiveTouch( $val ) { - $this->mTouchTmplLinks = (bool)$val; - if( $val ) // Cannot invalidate without queueRecursiveJobs() - $this->mRecursive = true; - } /** * Update link tables with outgoing links from an updated article @@ -95,7 +85,6 @@ class LinksUpdate { $this->doIncrementalUpdate(); } wfRunHooks( 'LinksUpdateComplete', array( &$this ) ); - } protected function doIncrementalUpdate() { @@ -207,49 +196,21 @@ class LinksUpdate { global $wgUpdateRowsPerJob; wfProfileIn( __METHOD__ ); - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'templatelinks', - array( 'tl_from' ), - array( - 'tl_namespace' => $this->mTitle->getNamespace(), - 'tl_title' => $this->mTitle->getDBkey() - ), __METHOD__ - ); - - $numRows = $res->numRows(); - if( !$numRows ) { + $cache = $this->mTitle->getBacklinkCache(); + $batches = $cache->partition( 'templatelinks', $wgUpdateRowsPerJob ); + if ( !$batches ) { wfProfileOut( __METHOD__ ); - return; // nothing to do + return; } - $numBatches = ceil( $numRows / $wgUpdateRowsPerJob ); - $realBatchSize = $numRows / $numBatches; - $start = false; $jobs = array(); - do { - for( $i = 0; $i <= $realBatchSize - 1; $i++ ) { - $row = $res->fetchRow(); - if( $row ) { - $id = $row[0]; - } else { - $id = false; - break; - } - } + foreach ( $batches as $batch ) { + list( $start, $end ) = $batch; $params = array( 'start' => $start, - 'end' => ( $id !== false ? $id - 1 : false ), + 'end' => $end, ); $jobs[] = new RefreshLinksJob2( $this->mTitle, $params ); - # Hit page caches while we're at it if set to do so... - if( $this->mTouchTmplLinks ) { - $params['table'] = 'templatelinks'; - $jobs[] = new HTMLCacheUpdateJob( $this->mTitle, $params ); - } - $start = $id; - } while ( $start ); - - $dbr->freeResult( $res ); - + } Job::batchInsert( $jobs ); wfProfileOut( __METHOD__ ); @@ -465,9 +426,12 @@ class LinksUpdate { * @private */ function getCategoryInsertions( $existing = array() ) { + global $wgContLang; $diffs = array_diff_assoc( $this->mCategories, $existing ); $arr = array(); foreach ( $diffs as $name => $sortkey ) { + $nt = Title::makeTitleSafe( NS_CATEGORY, $name ); + $wgContLang->findVariantLink( $name, $nt, true ); $arr[] = array( 'cl_from' => $this->mId, 'cl_to' => $name, diff --git a/includes/LogEventsList.php b/includes/LogEventsList.php index 528bd3aa..95109eb5 100644 --- a/includes/LogEventsList.php +++ b/includes/LogEventsList.php @@ -39,9 +39,10 @@ class LogEventsList { // Precache various messages if( !isset( $this->message ) ) { $messages = array( 'revertmerge', 'protect_change', 'unblocklink', 'change-blocklink', - 'revertmove', 'undeletelink', 'revdel-restore', 'rev-delundel', 'hist', 'pipe-separator' ); + 'revertmove', 'undeletelink', 'revdel-restore', 'rev-delundel', 'hist', 'diff', + 'pipe-separator' ); foreach( $messages as $msg ) { - $this->message[$msg] = wfMsgExt( $msg, array( 'escape' ) ); + $this->message[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) ); } } } @@ -65,16 +66,19 @@ class LogEventsList { * @param $pattern String * @param $year Integer: year * @param $month Integer: month - * @param $filter Boolean + * @param $filter: array + * @param $tagFilter: array? */ public function showOptions( $type = '', $user = '', $page = '', $pattern = '', $year = '', - $month = '', $filter = null ) + $month = '', $filter = null, $tagFilter='' ) { global $wgScript, $wgMiserMode; $action = htmlspecialchars( $wgScript ); $title = SpecialPage::getTitleFor( 'Log' ); $special = htmlspecialchars( $title->getPrefixedDBkey() ); + $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter ); + $this->out->addHTML( "<form action=\"$action\" method=\"get\"><fieldset>" . Xml::element( 'legend', array(), wfMsg( 'log' ) ) . Xml::hidden( 'title', $special ) . "\n" . @@ -82,28 +86,31 @@ class LogEventsList { $this->getUserInput( $user ) . "\n" . $this->getTitleInput( $page ) . "\n" . ( !$wgMiserMode ? ($this->getTitlePattern( $pattern )."\n") : "" ) . - "<p>" . $this->getDateMenu( $year, $month ) . "\n" . - ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . + "<p>" . Xml::dateMenu( $year, $month ) . "\n" . + ( $tagSelector ? Xml::tags( 'p', null, implode( ' ', $tagSelector ) ) :'' ). "\n" . + ( $filter ? "</p><p>".$this->getFilterLinks( $type, $filter )."\n" : "" ) . "\n" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "</p>\n" . "</fieldset></form>" ); } private function getFilterLinks( $logType, $filter ) { - global $wgTitle; + global $wgTitle, $wgLang; // show/hide links $messages = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); // Option value -> message mapping $links = array(); + $hiddens = ''; // keep track for "go" button foreach( $filter as $type => $val ) { $hideVal = 1 - intval($val); $link = $this->skin->makeKnownLinkObj( $wgTitle, $messages[$hideVal], wfArrayToCGI( array( "hide_{$type}_log" => $hideVal ), $this->getDefaultQuery() ) ); $links[$type] = wfMsgHtml( "log-show-hide-{$type}", $link ); + $hiddens .= Xml::hidden( "hide_{$type}_log", $val ) . "\n"; } // Build links - return implode( ' | ', $links ); + return '<small>'.$wgLang->pipeList( $links ) . '</small>' . $hiddens; } private function getDefaultQuery() { @@ -163,7 +170,7 @@ class LogEventsList { * @return String: Formatted HTML */ private function getUserInput( $user ) { - return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'user', 15, $user ); + return Xml::inputLabel( wfMsg( 'specialloguserlabel' ), 'user', 'mw-log-user', 15, $user ); } /** @@ -171,38 +178,7 @@ class LogEventsList { * @return String: Formatted HTML */ private function getTitleInput( $title ) { - return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'page', 20, $title ); - } - - /** - * @param $year Integer - * @param $month Integer - * @return string Formatted HTML - */ - private function getDateMenu( $year, $month ) { - # Offset overrides year/month selection - if( $month && $month !== -1 ) { - $encMonth = intval( $month ); - } else { - $encMonth = ''; - } - if ( $year ) { - $encYear = intval( $year ); - } else if( $encMonth ) { - $thisMonth = intval( gmdate( 'n' ) ); - $thisYear = intval( gmdate( 'Y' ) ); - if( intval($encMonth) > $thisMonth ) { - $thisYear--; - } - $encYear = $thisYear; - } else { - $encYear = ''; - } - return Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . - ' '. - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $encMonth, -1 ); + return Xml::inputLabel( wfMsg( 'speciallogtitlelabel' ), 'page', 'mw-log-page', 20, $title ); } /** @@ -230,6 +206,7 @@ class LogEventsList { global $wgLang, $wgUser, $wgContLang; $title = Title::makeTitle( $row->log_namespace, $row->log_title ); + $classes = array( "mw-logline-{$row->log_type}" ); $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $row->log_timestamp), true ); // User links if( self::isDeleted($row,LogPage::DELETED_USER) ) { @@ -276,7 +253,7 @@ class LogEventsList { array(), array( 'action' => 'unblock', 'ip' => $row->log_title ), 'known' ) - . ' ' . $this->message['pipe-separator'] . ' ' . + . $this->message['pipe-separator'] . $this->skin->link( SpecialPage::getTitleFor( 'Blockip', $row->log_title ), $this->message['change-blocklink'], array(), array(), 'known' ) . @@ -289,7 +266,7 @@ class LogEventsList { array(), array( 'action' => 'history', 'offset' => $row->log_timestamp ) ); if( $wgUser->isAllowed( 'protect' ) ) { - $revert .= ' ' . $this->message['pipe-separator'] . ' ' . + $revert .= $this->message['pipe-separator'] . $this->skin->link( $title, $this->message['protect_change'], array(), @@ -315,8 +292,17 @@ class LogEventsList { foreach( $Ids as $n => $id ) { $revParams .= '&' . urlencode($key) . '[]=' . urlencode($id); } - $revert = '(' . $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'], - 'target=' . $title->getPrefixedUrl() . $revParams ) . ')'; + $revert = array(); + // Diff link for single rev deletions + if( $key === 'oldid' && count($Ids) == 1 ) { + $token = urlencode( $wgUser->editToken( intval($Ids[0]) ) ); + $revert[] = $this->skin->makeKnownLinkObj( $title, $this->message['diff'], + 'diff='.intval($Ids[0])."&unhide=1&token=$token" ); + } + // View/modify link... + $revert[] = $this->skin->makeKnownLinkObj( $revdel, $this->message['revdel-restore'], + 'target=' . $title->getPrefixedUrl() . $revParams ); + $revert = '(' . implode(' | ',$revert) . ')'; } // Hidden log items, give review link } else if( self::typeAction($row,array('delete','suppress'),'event','deleterevision') ) { @@ -357,12 +343,16 @@ class LogEventsList { $this->skin, $paramArray, true ); } + // Any tags... + list($tagDisplay, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'logevent' ); + $classes = array_merge( $classes, $newClasses ); + if( $revert != '' ) { $revert = '<span class="mw-logevent-actionlink">' . $revert . '</span>'; } - return Xml::tags( 'li', array( "class" => "mw-logline-$row->log_type" ), - $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert ); + return Xml::tags( 'li', array( "class" => implode( ' ', $classes ) ), + $del . $time . ' ' . $userLink . ' ' . $action . ' ' . $comment . ' ' . $revert . " $tagDisplay" ) . "\n"; } /** @@ -373,19 +363,18 @@ class LogEventsList { $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); // If event was hidden from sysops if( !self::userCan( $row, LogPage::DELETED_RESTRICTED ) ) { - $del = $this->message['rev-delundel']; + $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' ); } else if( $row->log_type == 'suppress' ) { // No one should be hiding from the oversight log - $del = $this->message['rev-delundel']; + $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' ); } else { $target = SpecialPage::getTitleFor( 'Log', $row->log_type ); - $del = $this->skin->makeKnownLinkObj( $revdel, $this->message['rev-delundel'], - 'target=' . $target->getPrefixedUrl() . '&logid='.$row->log_id ); - // Bolden oversighted content - if( self::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ) - $del = "<strong>$del</strong>"; + $query = array( 'target' => $target->getPrefixedDBkey(), + 'logid[]' => $row->log_id + ); + $del = $this->skin->revDeleteLink( $query, self::isDeleted( $row, LogPage::DELETED_RESTRICTED ) ); } - return "<tt>(<small>$del</small>)</tt>"; + return $del; } /** @@ -468,15 +457,16 @@ class LogEventsList { /** * SQL clause to skip forbidden log types for this user * @param $db Database + * @param $audience string, public/user * @return mixed (string or false) */ - public static function getExcludeClause( $db ) { + public static function getExcludeClause( $db, $audience = 'public' ) { global $wgLogRestrictions, $wgUser; // Reset the array, clears extra "where" clauses when $par is used $hiddenLogs = array(); // Don't show private logs to unprivileged users foreach( $wgLogRestrictions as $logType => $right ) { - if( !$wgUser->isAllowed($right) ) { + if( $audience == 'public' || !$wgUser->isAllowed($right) ) { $safeType = $db->strencode( $logType ); $hiddenLogs[] = $safeType; } @@ -509,17 +499,18 @@ class LogPager extends ReverseChronologicalPager { * @param $month Integer */ public function __construct( $list, $type = '', $user = '', $title = '', $pattern = '', - $conds = array(), $year = false, $month = false ) + $conds = array(), $year = false, $month = false, $tagFilter = '' ) { parent::__construct(); $this->mConds = $conds; $this->mLogEventsList = $list; - $this->limitType( $type ); + $this->limitType( $type ); // also excludes hidden types $this->limitUser( $user ); $this->limitTitle( $title, $pattern ); $this->getDateCond( $year, $month ); + $this->mTagFilter = $tagFilter; } public function getDefaultQuery() { @@ -560,16 +551,17 @@ class LogPager extends ReverseChronologicalPager { if( isset($wgLogRestrictions[$type]) && !$wgUser->isAllowed($wgLogRestrictions[$type]) ) { $type = ''; } - // Don't show private logs to unpriviledged users - $hideLogs = LogEventsList::getExcludeClause( $this->mDb ); + // Don't show private logs to unpriviledged users. + // Also, only show them upon specific request to avoid suprises. + $audience = $type ? 'user' : 'public'; + $hideLogs = LogEventsList::getExcludeClause( $this->mDb, $audience ); if( $hideLogs !== false ) { $this->mConds[] = $hideLogs; } - if( !$type ) { - return false; + if( $type ) { + $this->type = $type; + $this->mConds['log_type'] = $type; } - $this->type = $type; - $this->mConds['log_type'] = $type; } /** @@ -591,7 +583,12 @@ class LogPager extends ReverseChronologicalPager { but for now it won't pass anywhere behind the optimizer */ $this->mConds[] = "NULL"; } else { + global $wgUser; $this->mConds['log_user'] = $userid; + // Paranoia: avoid brute force searches (bug 17342) + if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + $this->mConds[] = 'log_deleted & ' . LogPage::DELETED_USER . ' = 0'; + } $this->user = $usertitle->getText(); } } @@ -603,7 +600,7 @@ class LogPager extends ReverseChronologicalPager { * @param $pattern String */ private function limitTitle( $page, $pattern ) { - global $wgMiserMode; + global $wgMiserMode, $wgUser; $title = Title::newFromText( $page ); if( strlen($page) == 0 || !$title instanceof Title ) @@ -632,6 +629,10 @@ class LogPager extends ReverseChronologicalPager { $this->mConds['log_namespace'] = $ns; $this->mConds['log_title'] = $title->getDBkey(); } + // Paranoia: avoid brute force searches (bug 17342) + if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + $this->mConds[] = 'log_deleted & ' . LogPage::DELETED_ACTION . ' = 0'; + } } public function getQueryInfo() { @@ -644,13 +645,19 @@ class LogPager extends ReverseChronologicalPager { } else { $index = array( 'USE INDEX' => array( 'logging' => 'times' ) ); } - return array( + $info = array( 'tables' => array( 'logging', 'user' ), 'fields' => array( 'log_type', 'log_action', 'log_user', 'log_namespace', 'log_title', 'log_params', 'log_comment', 'log_id', 'log_deleted', 'log_timestamp', 'user_name', 'user_editcount' ), 'conds' => $this->mConds, - 'options' => $index + 'options' => $index, + 'join_conds' => array( 'user' => array( 'INNER JOIN', 'user_id=log_user' ) ), ); + + ChangeTags::modifyDisplayQuery( $info['tables'], $info['fields'], $info['conds'], + $info['join_conds'], $info['options'], $this->mTagFilter ); + + return $info; } function getIndexField() { @@ -701,6 +708,10 @@ class LogPager extends ReverseChronologicalPager { public function getMonth() { return $this->mMonth; } + + public function getTagFilter() { + return $this->mTagFilter; + } } /** @@ -722,6 +733,7 @@ class LogReader { $pattern = $request->getBool( 'pattern' ); $year = $request->getIntOrNull( 'year' ); $month = $request->getIntOrNull( 'month' ); + $tagFilter = $request->getVal( 'tagfilter' ); # Don't let the user get stuck with a certain date $skip = $request->getText( 'offset' ) || $request->getText( 'dir' ) == 'prev'; if( $skip ) { @@ -730,7 +742,7 @@ class LogReader { } # Use new list class to output results $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); - $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month ); + $this->pager = new LogPager( $loglist, $type, $user, $title, $pattern, $year, $month, $tagFilter ); } /** diff --git a/includes/LogPage.php b/includes/LogPage.php index 50a9a232..0d572385 100644 --- a/includes/LogPage.php +++ b/includes/LogPage.php @@ -37,7 +37,7 @@ class LogPage { /* @access private */ var $type, $action, $comment, $params, $target, $doer; /* @acess public */ - var $updateRecentChanges; + var $updateRecentChanges, $sendToUDP; /** * Constructor @@ -45,15 +45,16 @@ class LogPage { * @param string $type One of '', 'block', 'protect', 'rights', 'delete', * 'upload', 'move' * @param bool $rc Whether to update recent changes as well as the logging table + * @param bool $udp Whether to send to the UDP feed if NOT sent to RC */ - function __construct( $type, $rc = true ) { + public function __construct( $type, $rc = true, $udp = 'skipUDP' ) { $this->type = $type; $this->updateRecentChanges = $rc; + $this->sendToUDP = ($udp == 'UDP'); } protected function saveContent() { - global $wgUser, $wgLogRestrictions; - $fname = 'LogPage::saveContent'; + global $wgLogRestrictions; $dbw = wfGetDB( DB_MASTER ); $log_id = $dbw->nextSequenceValue( 'log_log_id_seq' ); @@ -70,21 +71,25 @@ class LogPage { 'log_comment' => $this->comment, 'log_params' => $this->params ); - $dbw->insert( 'logging', $data, $fname ); + $dbw->insert( 'logging', $data, __METHOD__ ); $newId = !is_null($log_id) ? $log_id : $dbw->insertId(); - if( !($dbw->affectedRows() > 0) ) { - wfDebugLog( "logging", "LogPage::saveContent failed to insert row - Error {$dbw->lastErrno()}: {$dbw->lastError()}" ); - } # And update recentchanges if( $this->updateRecentChanges ) { - # Don't add private logs to RC! - if( !isset($wgLogRestrictions[$this->type]) || $wgLogRestrictions[$this->type]=='*' ) { - $titleObj = SpecialPage::getTitleFor( 'Log', $this->type ); - $rcComment = $this->getRcComment(); - RecentChange::notifyLog( $now, $titleObj, $this->doer, $rcComment, '', - $this->type, $this->action, $this->target, $this->comment, $this->params, $newId ); + $titleObj = SpecialPage::getTitleFor( 'Log', $this->type ); + RecentChange::notifyLog( $now, $titleObj, $this->doer, $this->getRcComment(), '', $this->type, + $this->action, $this->target, $this->comment, $this->params, $newId ); + } else if( $this->sendToUDP ) { + # Don't send private logs to UDP + if( isset($wgLogRestrictions[$this->type]) && $wgLogRestrictions[$this->type] !='*' ) { + return true; } + # Notify external application via UDP. + # We send this to IRC but do not want to add it the RC table. + $titleObj = SpecialPage::getTitleFor( 'Log', $this->type ); + $rc = RecentChange::newLogEntry( $now, $titleObj, $this->doer, $this->getRcComment(), '', + $this->type, $this->action, $this->target, $this->comment, $this->params, $newId ); + $rc->notifyRC2UDP(); } return true; } @@ -98,7 +103,7 @@ class LogPage { if ($rcComment == '') $rcComment = $this->comment; else - $rcComment .= ': ' . $this->comment; + $rcComment .= wfMsgForContent( 'colon-separator' ) . $this->comment; } return $rcComment; } @@ -145,7 +150,7 @@ class LogPage { * @param string $type logtype * @return string Headertext of this logtype */ - static function logHeader( $type ) { + public static function logHeader( $type ) { global $wgLogHeaders, $wgMessageCache; $wgMessageCache->loadAllMessages(); return wfMsgExt($wgLogHeaders[$type],array('parseinline')); @@ -155,7 +160,7 @@ class LogPage { * @static * @return HTML string */ - static function actionText( $type, $action, $title = NULL, $skin = NULL, + public static function actionText( $type, $action, $title = NULL, $skin = NULL, $params = array(), $filterWikilinks = false ) { global $wgLang, $wgContLang, $wgLogActions, $wgMessageCache; @@ -196,7 +201,7 @@ class LogPage { } else { $details = ''; array_unshift( $params, $titleLink ); - if ( $key == 'block/block' || $key == 'suppress/block' || $key == 'block/reblock' ) { + if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) { if ( $skin ) { $params[1] = '<span title="' . htmlspecialchars( $params[1] ). '">' . $wgLang->translateBlockExpiry( $params[1] ) . '</span>'; @@ -208,11 +213,19 @@ class LogPage { } else if ( $type == 'protect' && count($params) == 3 ) { $details .= " {$params[1]}"; // restrictions and expiries if( $params[2] ) { - $details .= ' ['.wfMsg('protect-summary-cascade').']'; + if ( $skin ) { + $details .= ' ['.wfMsg('protect-summary-cascade').']'; + } else { + $details .= ' ['.wfMsgForContent('protect-summary-cascade').']'; + } } } else if ( $type == 'move' && count( $params ) == 3 ) { if( $params[2] ) { - $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']'; + if ( $skin ) { + $details .= ' [' . wfMsg( 'move-redirect-suppressed' ) . ']'; + } else { + $details .= ' [' . wfMsgForContent( 'move-redirect-suppressed' ) . ']'; + } } } $rv = wfMsgReal( $wgLogActions[$key], $params, true, !$skin ) . $details; @@ -228,6 +241,17 @@ class LogPage { $rv = "$action"; } } + + // For the perplexed, this feature was added in r7855 by Erik. + // The feature was added because we liked adding [[$1]] in our log entries + // but the log entries are parsed as Wikitext on RecentChanges but as HTML + // on Special:Log. The hack is essentially that [[$1]] represented a link + // to the title in question. The first parameter to the HTML version (Special:Log) + // is that link in HTML form, and so this just gets rid of the ugly [[]]. + // However, this is a horrible hack and it doesn't work like you expect if, say, + // you want to link to something OTHER than the title of the log entry. + // The real problem, which Erik was trying to fix (and it sort-of works now) is + // that the same messages are being treated as both wikitext *and* HTML. if( $filterWikilinks ) { $rv = str_replace( "[[", "", $rv ); $rv = str_replace( "]]", "", $rv ); @@ -296,7 +320,7 @@ class LogPage { * @param array $params Parameters passed later to wfMsg.* functions * @param User $doer The user doing the action */ - function addEntry( $action, $target, $comment, $params = array(), $doer = null ) { + public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) { if ( !is_array( $params ) ) { $params = array( $params ); } @@ -324,7 +348,7 @@ class LogPage { * Create a blob from a parameter array * @static */ - static function makeParamBlob( $params ) { + public static function makeParamBlob( $params ) { return implode( "\n", $params ); } @@ -332,7 +356,7 @@ class LogPage { * Extract a parameter array from a blob * @static */ - static function extractParams( $blob ) { + public static function extractParams( $blob ) { if ( $blob === '' ) { return array(); } else { @@ -350,11 +374,13 @@ class LogPage { * @return string */ public static function formatBlockFlags( $flags, $forContent = false ) { + global $wgLang; + $flags = explode( ',', trim( $flags ) ); if( count( $flags ) > 0 ) { for( $i = 0; $i < count( $flags ); $i++ ) $flags[$i] = self::formatBlockFlag( $flags[$i], $forContent ); - return '(' . implode( ', ', $flags ) . ')'; + return '(' . $wgLang->commaList( $flags ) . ')'; } else { return ''; } diff --git a/includes/MagicWord.php b/includes/MagicWord.php index 5b5b77f0..4e97016d 100644 --- a/includes/MagicWord.php +++ b/includes/MagicWord.php @@ -78,6 +78,7 @@ class MagicWord { 'revisionmonth', 'revisionyear', 'revisiontimestamp', + 'revisionuser', 'subpagename', 'subpagenamee', 'displaytitle', @@ -90,7 +91,9 @@ class MagicWord { 'subjectpagename', 'subjectpagenamee', 'numberofusers', + 'numberofactiveusers', 'newsectionlink', + 'nonewsectionlink', 'numberofpages', 'currentversion', 'basepagename', @@ -141,6 +144,7 @@ class MagicWord { 'localweek' => 3600, 'localdow' => 3600, 'numberofusers' => 3600, + 'numberofactiveusers' => 3600, 'numberofpages' => 3600, 'currentversion' => 86400, 'currenttimestamp' => 3600, @@ -158,6 +162,7 @@ class MagicWord { 'toc', 'noeditsection', 'newsectionlink', + 'nonewsectionlink', 'hiddencat', 'index', 'noindex', diff --git a/includes/MessageCache.php b/includes/MessageCache.php index a06b0cb9..2236bdd7 100644 --- a/includes/MessageCache.php +++ b/includes/MessageCache.php @@ -702,7 +702,10 @@ class MessageCache { * @param string $lang The messages language, English by default */ function addMessage( $key, $value, $lang = 'en' ) { - $this->mExtensionMessages[$lang][$key] = $value; + global $wgContLang; + # Normalise title-case input + $lckey = str_replace( ' ', '_', $wgContLang->lcfirst( $key ) ); + $this->mExtensionMessages[$lang][$lckey] = $value; } /** @@ -800,6 +803,7 @@ class MessageCache { */ function loadMessagesFile( $filename, $langcode = false ) { global $wgLang, $wgContLang; + wfProfileIn( __METHOD__ ); $messages = $magicWords = false; require( $filename ); @@ -822,6 +826,7 @@ class MessageCache { global $wgContLang; $wgContLang->addMagicWordsByLang( $magicWords ); } + wfProfileOut( __METHOD__ ); } /** @@ -831,6 +836,7 @@ class MessageCache { * @param string $langcode Language code to process. */ function processMessagesArray( $messages, $langcode ) { + wfProfileIn( __METHOD__ ); $fallbackCode = $langcode; $mergedMessages = array(); do { @@ -842,6 +848,7 @@ class MessageCache { if ( !empty($mergedMessages) ) $this->addMessages( $mergedMessages, $langcode ); + wfProfileOut( __METHOD__ ); } public function figureMessage( $key ) { diff --git a/includes/MimeMagic.php b/includes/MimeMagic.php index 4797752d..d52de994 100644 --- a/includes/MimeMagic.php +++ b/includes/MimeMagic.php @@ -579,22 +579,22 @@ class MimeMagic { */ function detectZipType( $header ) { $opendocTypes = array( - 'chart', 'chart-template', - 'formula', + 'chart', 'formula-template', - 'graphics', + 'formula', 'graphics-template', - 'image', + 'graphics', 'image-template', - 'presentation', + 'image', 'presentation-template', - 'spreadsheet', + 'presentation', 'spreadsheet-template', - 'text', + 'spreadsheet', 'text-template', 'text-master', - 'text-web' ); + 'text-web', + 'text' ); // http://lists.oasis-open.org/archives/office/200505/msg00006.html $types = '(?:' . implode( '|', $opendocTypes ) . ')'; diff --git a/includes/OutputHandler.php b/includes/OutputHandler.php index 2b3e9fae..1f4798b7 100644 --- a/includes/OutputHandler.php +++ b/includes/OutputHandler.php @@ -10,7 +10,7 @@ function wfOutputHandler( $s ) { $headers = apache_response_headers(); $isHTML = true; foreach ( $headers as $name => $value ) { - if ( strtolower( $name ) == 'content-type' && strpos( $value, 'text/html' ) === false ) { + if ( strtolower( $name ) == 'content-type' && strpos( $value, 'text/html' ) === false && strpos( $value, 'application/xhtml+xml' ) === false ) { $isHTML = false; break; } @@ -123,10 +123,9 @@ function wfDoContentLength( $length ) { * Replace the output with an error if the HTML is not valid */ function wfHtmlValidationHandler( $s ) { - global $IP; - $tidy = new tidy; - $tidy->parseString( $s, "$IP/includes/tidy.conf", 'utf8' ); - if ( $tidy->getStatus() == 0 ) { + + $errors = ''; + if ( MWTidy::checkErrors( $s, $errors ) ) { return $s; } @@ -134,7 +133,7 @@ function wfHtmlValidationHandler( $s ) { $out = <<<EOT <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> -<html> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr"> <head> <title>HTML validation error</title> <style> @@ -147,7 +146,7 @@ li { white-space: pre } <ul> EOT; - $error = strtok( $tidy->errorBuffer, "\n" ); + $error = strtok( $errors, "\n" ); $badLines = array(); while ( $error !== false ) { if ( preg_match( '/^line (\d+)/', $error, $m ) ) { @@ -158,8 +157,9 @@ EOT; $error = strtok( "\n" ); } - $out .= '<pre>' . htmlspecialchars( $tidy->errorBuffer ) . '</pre>'; - $out .= '<ol>'; + $out .= '</ul>'; + $out .= '<pre>' . htmlspecialchars( $errors ) . '</pre>'; + $out .= "<ol>\n"; $line = strtok( $s, "\n" ); $i = 1; while ( $line !== false ) { @@ -168,7 +168,7 @@ EOT; } else { $out .= '<li>'; } - $out .= htmlspecialchars( $line ) . '</li>'; + $out .= htmlspecialchars( $line ) . "</li>\n"; $line = strtok( "\n" ); $i++; } diff --git a/includes/OutputPage.php b/includes/OutputPage.php index f8dba714..ed9a43d3 100644 --- a/includes/OutputPage.php +++ b/includes/OutputPage.php @@ -29,6 +29,7 @@ class OutputPage { var $mArticleBodyOnly = false; var $mNewSectionLink = false; + var $mHideNewSectionLink = false; var $mNoGallery = false; var $mPageTitleActionText = ''; var $mParseWarnings = array(); @@ -165,7 +166,7 @@ class OutputPage { * * @return bool True iff cache-ok headers was sent. */ - function checkLastModified ( $timestamp ) { + function checkLastModified( $timestamp ) { global $wgCachePages, $wgCacheEpoch, $wgUser, $wgRequest; if ( !$timestamp || $timestamp == '19700101000000' ) { @@ -232,6 +233,7 @@ class OutputPage { # Not modified # Give a 304 response code and disable body output wfDebug( __METHOD__ . ": NOT MODIFIED, $info\n", false ); + ini_set('zlib.output_compression', 0); $wgRequest->response()->header( "HTTP/1.1 304 Not Modified" ); $this->sendCacheControl(); $this->disable(); @@ -309,20 +311,20 @@ class OutputPage { } } - public function setHTMLTitle( $name ) {$this->mHTMLtitle = $name; } + public function setHTMLTitle( $name ) { $this->mHTMLtitle = $name; } public function setPageTitle( $name ) { - global $action, $wgContLang; - $name = $wgContLang->convert($name, true); + global $wgContLang; + $name = $wgContLang->convert( $name, true ); $this->mPagetitle = $name; - if(!empty($action)) { - $taction = $this->getPageTitleActionText(); - if( !empty( $taction ) ) { - $name .= ' - '.$taction; - } + + $taction = $this->getPageTitleActionText(); + if( !empty( $taction ) ) { + $name .= ' - '.$taction; } $this->setHTMLTitle( wfMsg( 'pagetitle', $name ) ); } + public function getHTMLTitle() { return $this->mHTMLtitle; } public function getPageTitle() { return $this->mPagetitle; } public function setSubtitle( $str ) { $this->mSubtitle = /*$this->parse(*/$str/*)*/; } // @bug 2514 @@ -338,6 +340,7 @@ class OutputPage { public function setOnloadHandler( $js ) { $this->mOnloadHandler = $js; } public function getOnloadHandler() { return $this->mOnloadHandler; } public function disable() { $this->mDoNothing = true; } + public function isDisabled() { return $this->mDoNothing; } public function setArticleRelated( $v ) { $this->mIsArticleRelated = $v; @@ -408,7 +411,12 @@ class OutputPage { if ( wfRunHooks( 'OutputPageMakeCategoryLinks', array( &$this, $categories, &$this->mCategoryLinks ) ) ) { $sk = $wgUser->getSkin(); foreach ( $categories as $category => $type ) { + $origcategory = $category; $title = Title::makeTitleSafe( NS_CATEGORY, $category ); + $wgContLang->findVariantLink( $category, $title, true ); + if ( $category != $origcategory ) + if ( array_key_exists( $category, $categories ) ) + continue; $text = $wgContLang->convertHtml( $title->getText() ); $this->mCategoryLinks[$type][] = $sk->makeLinkObj( $title, $text ); } @@ -511,6 +519,7 @@ class OutputPage { $this->mLanguageLinks += $parserOutput->getLanguageLinks(); $this->addCategoryLinks( $parserOutput->getCategories() ); $this->mNewSectionLink = $parserOutput->getNewSection(); + $this->mHideNewSectionLink = $parserOutput->getHideNewSection(); if( is_null( $wgExemptFromUserRobotsControl ) ) { $bannedNamespaces = $wgContentNamespaces; @@ -538,9 +547,11 @@ class OutputPage { $this->mTemplateIds[$ns] = $dbks; } } - // Display title + // Page title if( ( $dt = $parserOutput->getDisplayTitle() ) !== false ) $this->setPageTitle( $dt ); + else if ( ( $title = $parserOutput->getTitleText() ) != '' ) + $this->setPageTitle( $title ); // Hooks registered in the object global $wgParserOutputHooks; @@ -586,7 +597,7 @@ class OutputPage { $popts->setTidy(false); if ( $cache && $article && $parserOutput->getCacheTime() != -1 ) { $parserCache = ParserCache::singleton(); - $parserCache->save( $parserOutput, $article, $wgUser ); + $parserCache->save( $parserOutput, $article, $popts); } $this->addParserOutput( $parserOutput ); @@ -642,15 +653,27 @@ class OutputPage { return $parserOutput->getText(); } + /** Parse wikitext, strip paragraphs, and return the HTML. */ + public function parseInline( $text, $linestart = true, $interface = false ) { + $parsed = $this->parse( $text, $linestart, $interface ); + + $m = array(); + if ( preg_match( '/^<p>(.*)\n?<\/p>\n?/sU', $parsed, $m ) ) { + $parsed = $m[1]; + } + + return $parsed; + } + /** * @param Article $article * @param User $user * * @return bool True if successful, else false. */ - public function tryParserCache( &$article, $user ) { + public function tryParserCache( &$article ) { $parserCache = ParserCache::singleton(); - $parserOutput = $parserCache->get( $article, $user ); + $parserOutput = $parserCache->get( $article, $this->parserOptions() ); if ( $parserOutput !== false ) { $this->addParserOutput( $parserOutput ); return true; @@ -917,13 +940,13 @@ class OutputPage { 'rel' => 'alternate', 'type' => 'application/x-wiki', 'title' => wfMsg( 'edit' ), - 'href' => $wgTitle->getFullURL( 'action=edit' ) + 'href' => $wgTitle->getLocalURL( 'action=edit' ) ) ); // Alternate edit link $this->addLink( array( 'rel' => 'edit', 'title' => wfMsg( 'edit' ), - 'href' => $wgTitle->getFullURL( 'action=edit' ) + 'href' => $wgTitle->getLocalURL( 'action=edit' ) ) ); } } @@ -1132,7 +1155,7 @@ class OutputPage { * @param string $permission key required */ public function permissionRequired( $permission ) { - global $wgUser; + global $wgUser, $wgLang; $this->setPageTitle( wfMsg( 'badaccess' ) ); $this->setHTMLTitle( wfMsg( 'errorpagetitle' ) ); @@ -1144,7 +1167,7 @@ class OutputPage { User::getGroupsWithPermission( $permission ) ); if( $groups ) { $this->addWikiMsg( 'badaccess-groups', - implode( ', ', $groups ), + $wgLang->commaList( $groups ), count( $groups) ); } else { $this->addWikiMsg( 'badaccess-group0' ); @@ -1457,7 +1480,7 @@ class OutputPage { $ret = ''; if( $wgMimeType == 'text/xml' || $wgMimeType == 'application/xhtml+xml' || $wgMimeType == 'application/xml' ) { - $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?>\n"; + $ret .= "<?xml version=\"1.0\" encoding=\"$wgOutputEncoding\" ?" . ">\n"; } $ret .= "<!DOCTYPE html PUBLIC \"$wgDocType\"\n \"$wgDTD\">\n"; @@ -1505,7 +1528,7 @@ class OutputPage { if ( count( $this->mKeywords ) > 0 ) { $strip = array( - "/<.*?>/" => '', + "/<.*?" . ">/" => '', "/_/" => ' ' ); $this->addMeta( 'keywords', preg_replace(array_keys($strip), array_values($strip),implode( ",", $this->mKeywords ) ) ); @@ -1576,7 +1599,7 @@ class OutputPage { foreach( $wgFeedClasses as $format => $class ) { $tags[] = $this->feedLink( $format, - $rctitle->getFullURL( "feed={$format}" ), + $rctitle->getLocalURL( "feed={$format}" ), wfMsg( "site-{$format}-feed", $wgSitename ) ); # For grep: 'site-rss-feed', 'site-atom-feed'. } } @@ -1759,6 +1782,15 @@ class OutputPage { } /** + * Forcibly hide the new section link? + * + * @return bool + */ + public function forceHideNewSectionLink() { + return $this->mHideNewSectionLink; + } + + /** * Show a warning about slave lag * * If the lag is higher than $wgSlaveLagCritical seconds, @@ -1843,7 +1875,7 @@ class OutputPage { $args = array(); $name = $spec; } - $s = str_replace( '$' . ($n+1), wfMsgExt( $name, $options, $args ), $s ); + $s = str_replace( '$' . ( $n + 1 ), wfMsgExt( $name, $options, $args ), $s ); } $this->addHTML( $this->parse( $s, /*linestart*/true, /*uilang*/true ) ); } diff --git a/includes/PageHistory.php b/includes/PageHistory.php index b01b485e..9477981f 100644 --- a/includes/PageHistory.php +++ b/includes/PageHistory.php @@ -23,6 +23,8 @@ class PageHistory { var $lastdate; var $linesonpage; var $mLatestId = null; + + private $mOldIdChecked = 0; /** * Construct a new PageHistory. @@ -112,6 +114,8 @@ class PageHistory { */ $year = $wgRequest->getInt( 'year' ); $month = $wgRequest->getInt( 'month' ); + $tagFilter = $wgRequest->getVal( 'tagfilter' ); + $tagSelector = ChangeTags::buildTagFilterSelector( $tagFilter ); $action = htmlspecialchars( $wgScript ); $wgOut->addHTML( @@ -119,7 +123,8 @@ class PageHistory { Xml::fieldset( wfMsg( 'history-fieldset-title' ), false, array( 'id' => 'mw-history-search' ) ) . Xml::hidden( 'title', $this->mTitle->getPrefixedDBKey() ) . "\n" . Xml::hidden( 'action', 'history' ) . "\n" . - $this->getDateMenu( $year, $month ) . ' ' . + xml::dateMenu( $year, $month ) . ' ' . + ( $tagSelector ? ( implode( ' ', $tagSelector ) . ' ' ) : '' ) . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . '</fieldset></form>' ); @@ -129,7 +134,7 @@ class PageHistory { /** * Do the list */ - $pager = new PageHistoryPager( $this, $year, $month ); + $pager = new PageHistoryPager( $this, $year, $month, $tagFilter ); $this->linesonpage = $pager->getNumRows(); $wgOut->addHTML( $pager->getNavigationBar() . @@ -143,37 +148,6 @@ class PageHistory { } /** - * @return string Formatted HTML - * @param int $year - * @param int $month - */ - private function getDateMenu( $year, $month ) { - # Offset overrides year/month selection - if( $month && $month !== -1 ) { - $encMonth = intval( $month ); - } else { - $encMonth = ''; - } - if( $year ) { - $encYear = intval( $year ); - } else if( $encMonth ) { - $thisMonth = intval( gmdate( 'n' ) ); - $thisYear = intval( gmdate( 'Y' ) ); - if( intval($encMonth) > $thisMonth ) { - $thisYear--; - } - $encYear = $thisYear; - } else { - $encYear = ''; - } - return Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . - ' '. - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $encMonth, -1 ); - } - - /** * Creates begin of history list with a submit button * * @return string HTML output @@ -287,37 +261,34 @@ class PageHistory { $lastlink = $this->lastLink( $rev, $next, $counter ); $arbitrary = $this->diffButtons( $rev, $firstInList, $counter ); $link = $this->revLink( $rev ); + $classes = array(); $s = "($curlink) ($lastlink) $arbitrary"; if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); - if( $firstInList ) { + if( $latest ) { // We don't currently handle well changing the top revision's settings - $del = $this->message['rev-delundel']; + $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' ); } else if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { // If revision was hidden from sysops - $del = $this->message['rev-delundel']; + $del = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.$this->message['rev-delundel'].')' ); } else { - $del = $this->mSkin->makeKnownLinkObj( $revdel, - $this->message['rev-delundel'], - 'target=' . urlencode( $this->mTitle->getPrefixedDbkey() ) . - '&oldid=' . urlencode( $rev->getId() ) ); - // Bolden oversighted content - if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) - $del = "<strong>$del</strong>"; + $query = array( 'target' => $this->mTitle->getPrefixedDbkey(), + 'oldid' => $rev->getId() + ); + $del = $this->mSkin->revDeleteLink( $query, $rev->isDeleted( Revision::DELETED_RESTRICTED ) ); } - $s .= " <tt>(<small>$del</small>)</tt> "; + $s .= " $del "; } $s .= " $link"; $s .= " <span class='history-user'>" . $this->mSkin->revUserTools( $rev, true ) . "</span>"; - if( $row->rev_minor_edit ) { + if( $rev->isMinor() ) { $s .= ' ' . Xml::element( 'span', array( 'class' => 'minor' ), wfMsg( 'minoreditletter') ); } - if( !is_null( $size = $rev->getSize() ) && $rev->userCan( Revision::DELETED_TEXT ) ) { + if( !is_null( $size = $rev->getSize() ) && !$rev->isDeleted( Revision::DELETED_TEXT ) ) { $s .= ' ' . $this->mSkin->formatRevisionSize( $size ); } @@ -356,12 +327,19 @@ class PageHistory { } if( $tools ) { - $s .= ' (' . implode( ' | ', $tools ) . ')'; + $s .= ' (' . $wgLang->pipeList( $tools ) . ')'; } + # Tags + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'history' ); + $classes = array_merge( $classes, $newClasses ); + $s .= " $tagSummary"; + wfRunHooks( 'PageHistoryLineEnding', array( $this, &$row , &$s ) ); - return "<li>$s</li>\n"; + $classes = implode( ' ', $classes ); + + return "<li class=\"$classes\">$s</li>\n"; } /** @@ -372,14 +350,10 @@ class PageHistory { function revLink( $rev ) { global $wgLang; $date = $wgLang->timeanddate( wfTimestamp(TS_MW, $rev->getTimestamp()), true ); - if( $rev->userCan( Revision::DELETED_TEXT ) ) { - $link = $this->mSkin->makeKnownLinkObj( - $this->mTitle, $date, "oldid=" . $rev->getId() ); + if( !$rev->isDeleted( Revision::DELETED_TEXT ) ) { + $link = $this->mSkin->makeKnownLinkObj( $this->mTitle, $date, "oldid=" . $rev->getId() ); } else { - $link = $date; - } - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - return '<span class="history-deleted">' . $link . '</span>'; + $link = '<span class="history-deleted">' . $date . '</span>'; } return $link; } @@ -392,7 +366,7 @@ class PageHistory { */ function curLink( $rev, $latest ) { $cur = $this->message['cur']; - if( $latest || !$rev->userCan( Revision::DELETED_TEXT ) ) { + if( $latest || $rev->isDeleted( Revision::DELETED_TEXT ) ) { return $cur; } else { return $this->mSkin->makeKnownLinkObj( $this->mTitle, $cur, @@ -418,7 +392,7 @@ class PageHistory { # Next row probably exists but is unknown, use an oldid=prev link return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last, "diff=" . $prevRev->getId() . "&oldid=prev" ); - } elseif( !$prevRev->userCan(Revision::DELETED_TEXT) || !$nextRev->userCan(Revision::DELETED_TEXT) ) { + } elseif( $prevRev->isDeleted(Revision::DELETED_TEXT) || $nextRev->isDeleted(Revision::DELETED_TEXT) ) { return $last; } else { return $this->mSkin->makeKnownLinkObj( $this->mTitle, $last, @@ -435,40 +409,29 @@ class PageHistory { * @return string HTML output for the radio buttons */ function diffButtons( $rev, $firstInList, $counter ) { - if( $this->linesonpage > 1) { - $radio = array( - 'type' => 'radio', - 'value' => $rev->getId(), - ); - - if( !$rev->userCan( Revision::DELETED_TEXT ) ) { - $radio['disabled'] = 'disabled'; - } - + if( $this->linesonpage > 1 ) { + $radio = array( 'type' => 'radio', 'value' => $rev->getId() ); /** @todo: move title texts to javascript */ if( $firstInList ) { - $first = Xml::element( 'input', array_merge( - $radio, - array( - 'style' => 'visibility:hidden', - 'name' => 'oldid' ) ) ); + $first = Xml::element( 'input', + array_merge( $radio, array( 'style' => 'visibility:hidden', 'name' => 'oldid' ) ) + ); $checkmark = array( 'checked' => 'checked' ); } else { - if( $counter == 2 ) { + # Check visibility of old revisions + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $radio['disabled'] = 'disabled'; + $checkmark = array(); // We will check the next possible one + } else if( $counter == 2 || !$this->mOldIdChecked ) { $checkmark = array( 'checked' => 'checked' ); + $this->mOldIdChecked = $rev->getId(); } else { $checkmark = array(); } - $first = Xml::element( 'input', array_merge( - $radio, - $checkmark, - array( 'name' => 'oldid' ) ) ); + $first = Xml::element( 'input', array_merge( $radio, $checkmark, array( 'name' => 'oldid' ) ) ); $checkmark = array(); } - $second = Xml::element( 'input', array_merge( - $radio, - $checkmark, - array( 'name' => 'diff' ) ) ); + $second = Xml::element( 'input', array_merge( $radio, $checkmark, array( 'name' => 'diff' ) ) ); return $first . $second; } else { return ''; @@ -573,7 +536,7 @@ class PageHistory { $rev->getUserText(), $wgContLang->timeanddate( $rev->getTimestamp() ) ); } else { - $title = $rev->getUserText() . ": " . FeedItem::stripComment( $rev->getComment() ); + $title = $rev->getUserText() . wfMsgForContent( 'colon-separator' ) . FeedItem::stripComment( $rev->getComment() ); } return new FeedItem( @@ -593,20 +556,28 @@ class PageHistory { class PageHistoryPager extends ReverseChronologicalPager { public $mLastRow = false, $mPageHistory, $mTitle; - function __construct( $pageHistory, $year='', $month='' ) { + function __construct( $pageHistory, $year='', $month='', $tagFilter = '' ) { parent::__construct(); $this->mPageHistory = $pageHistory; $this->mTitle =& $this->mPageHistory->mTitle; + $this->tagFilter = $tagFilter; $this->getDateCond( $year, $month ); } function getQueryInfo() { $queryInfo = array( 'tables' => array('revision'), - 'fields' => Revision::selectFields(), + 'fields' => array_merge( Revision::selectFields(), array('ts_tags') ), 'conds' => array('rev_page' => $this->mPageHistory->mTitle->getArticleID() ), - 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ) + 'options' => array( 'USE INDEX' => array('revision' => 'page_timestamp') ), + 'join_conds' => array( 'tag_summary' => array( 'LEFT JOIN', 'ts_rev_id=rev_id' ) ), ); + ChangeTags::modifyDisplayQuery( $queryInfo['tables'], + $queryInfo['fields'], + $queryInfo['conds'], + $queryInfo['join_conds'], + $queryInfo['options'], + $this->tagFilter ); wfRunHooks( 'PageHistoryPager::getQueryInfo', array( &$this, &$queryInfo ) ); return $queryInfo; } diff --git a/includes/Pager.php b/includes/Pager.php index 8ec32ff4..8faec533 100644 --- a/includes/Pager.php +++ b/includes/Pager.php @@ -304,20 +304,18 @@ abstract class IndexPager implements Pager { if ( $query === null ) { return $text; } - if( $type == 'prev' || $type == 'next' ) { - $attrs = "rel=\"$type\""; - } elseif( $type == 'first' ) { - $attrs = "rel=\"start\""; - } else { - # HTML 4 has no rel="end" . . . - $attrs = ''; + + $attrs = array(); + if( in_array( $type, array( 'first', 'prev', 'next', 'last' ) ) ) { + # HTML5 rel attributes + $attrs['rel'] = $type; } if( $type ) { - $attrs .= " class=\"mw-{$type}link\"" ; + $attrs['class'] = "mw-{$type}link"; } - return $this->getSkin()->makeKnownLinkObj( $this->getTitle(), $text, - wfArrayToCGI( $query, $this->getDefaultQuery() ), '', '', $attrs ); + return $this->getSkin()->link( $this->getTitle(), $text, + $attrs, $query + $this->getDefaultQuery(), array('noclasses','known') ); } /** @@ -532,10 +530,10 @@ abstract class AlphabeticPager extends IndexPager { $pagingLinks = $this->getPagingLinks( $linkTexts ); $limitLinks = $this->getLimitLinks(); - $limits = implode( ' | ', $limitLinks ); + $limits = $wgLang->pipeList( $limitLinks ); $this->mNavigationBar = - "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " . wfMsgHtml( 'viewprevnext', $pagingLinks['prev'], $pagingLinks['next'], $limits ); @@ -551,7 +549,7 @@ abstract class AlphabeticPager extends IndexPager { if( $first ) { $first = false; } else { - $extra .= ' | '; + $extra .= wfMsgExt( 'pipe-separator' , 'escapenoentities' ); } if( $order == $this->mOrderType ) { @@ -612,9 +610,9 @@ abstract class ReverseChronologicalPager extends IndexPager { $pagingLinks = $this->getPagingLinks( $linkTexts ); $limitLinks = $this->getLimitLinks(); - $limits = implode( ' | ', $limitLinks ); + $limits = $wgLang->pipeList( $limitLinks ); - $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + $this->mNavigationBar = "({$pagingLinks['first']}" . wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "{$pagingLinks['last']}) " . wfMsgHtml("viewprevnext", $pagingLinks['prev'], $pagingLinks['next'], $limits); return $this->mNavigationBar; } @@ -747,7 +745,8 @@ abstract class TablePager extends IndexPager { } function formatRow( $row ) { - $s = "<tr>\n"; + $rowClass = $this->getRowClass( $row ); + $s = "<tr class=\"$rowClass\">\n"; $fieldNames = $this->getFieldNames(); $this->mCurrentRow = $row; # In case formatValue needs to know foreach ( $fieldNames as $field => $name ) { @@ -763,6 +762,10 @@ abstract class TablePager extends IndexPager { return $s; } + function getRowClass($row) { + return ''; + } + function getIndexField() { return $this->mSort; } diff --git a/includes/PatrolLog.php b/includes/PatrolLog.php index 5f305c10..978821c1 100644 --- a/includes/PatrolLog.php +++ b/includes/PatrolLog.php @@ -14,22 +14,20 @@ class PatrolLog { * @param mixed $change Change identifier or RecentChange object * @param bool $auto Was this patrol event automatic? */ - public static function record( $change, $auto = false ) { - if( !( is_object( $change ) && $change instanceof RecentChange ) ) { - $change = RecentChange::newFromId( $change ); - if( !is_object( $change ) ) + public static function record( $rc, $auto = false ) { + if( !( $rc instanceof RecentChange ) ) { + $rc = RecentChange::newFromId( $rc ); + if( !is_object( $rc ) ) return false; } - $title = Title::makeTitleSafe( $change->getAttribute( 'rc_namespace' ), - $change->getAttribute( 'rc_title' ) ); + $title = Title::makeTitleSafe( $rc->getAttribute( 'rc_namespace' ), $rc->getAttribute( 'rc_title' ) ); if( is_object( $title ) ) { - $params = self::buildParams( $change, $auto ); - $log = new LogPage( 'patrol', false ); # False suppresses RC entries + $params = self::buildParams( $rc, $auto ); + $log = new LogPage( 'patrol', false, $auto ? "skipUDP" : "UDP" ); # False suppresses RC entries $log->addEntry( 'patrol', $title, '', $params ); return true; - } else { - return false; } + return false; } /** @@ -41,12 +39,8 @@ class PatrolLog { * @return string */ public static function makeActionText( $title, $params, $skin ) { - # This is a bit of a hack, but...if $skin is not a Skin, then *do nothing* - # -- this is fine, because the action text we would be queried for under - # these conditions would have gone into recentchanges, which we aren't - # supposed to be updating + list( $cur, /* $prev */, $auto ) = $params; if( is_object( $skin ) ) { - list( $cur, /* $prev */, $auto ) = $params; # Standard link to the page in question $link = $skin->makeLinkObj( $title ); if( $title->exists() ) { @@ -64,7 +58,8 @@ class PatrolLog { # Put it all together return wfMsgHtml( 'patrol-log-line', $diff, $link, $auto ); } else { - return ''; + $text = $title->getPrefixedText(); + return wfMsgForContent( 'patrol-log-line', wfMsgHtml('patrol-log-diff',$cur), "[[$text]]", '' ); } } diff --git a/includes/PrefixSearch.php b/includes/PrefixSearch.php index af569112..10c85930 100644 --- a/includes/PrefixSearch.php +++ b/includes/PrefixSearch.php @@ -135,7 +135,7 @@ class PrefixSearch { // Reformat useful data for future printing by JSON engine $srchres = array (); - foreach ($data['query']['allpages'] as & $pageinfo) { + foreach ((array)$data['query']['allpages'] as $pageinfo) { // Note: this data will no be printable by the xml engine // because it does not support lists of unnamed items $srchres[] = $pageinfo['title']; diff --git a/includes/Profiler.php b/includes/Profiler.php index ffb48978..80a6a68a 100644 --- a/includes/Profiler.php +++ b/includes/Profiler.php @@ -78,8 +78,8 @@ class Profiler { * @param $functionname string */ function profileIn( $functionname ) { - global $wgDebugFunctionEntry; - + global $wgDebugFunctionEntry, $wgProfiling; + if( !$wgProfiling ) return; if( $wgDebugFunctionEntry ){ $this->debug( str_repeat( ' ', count( $this->mWorkStack ) ) . 'Entering ' . $functionname . "\n" ); } @@ -92,8 +92,8 @@ class Profiler { * @param $functionname string */ function profileOut($functionname) { - global $wgDebugFunctionEntry; - + global $wgDebugFunctionEntry, $wgProfiling; + if( !$wgProfiling ) return; $memory = memory_get_usage(); $time = $this->getTime(); @@ -145,7 +145,12 @@ class Profiler { } $this->close(); - if( $wgProfileCallTree ){ + if( $wgProfileCallTree ) { + global $wgProfileToDatabase; + # XXX: We must call $this->getFunctionReport() to log to the DB + if( $wgProfileToDatabase ) { + $this->getFunctionReport(); + } return $this->getCallTree(); } else { return $this->getFunctionReport(); @@ -202,16 +207,13 @@ class Profiler { /** * Callback to get a formatted line for the call tree */ - function getCallTreeLine($entry) { + function getCallTreeLine( $entry ) { list( $fname, $level, $start, /* $x */, $end) = $entry; $delta = $end - $start; $space = str_repeat(' ', $level); - # The ugly double sprintf is to work around a PHP bug, # which has been fixed in recent releases. - return sprintf( "%10s %s %s\n", - trim( sprintf( "%7.3f", $delta * 1000.0 ) ), - $space, $fname ); + return sprintf( "%10s %s %s\n", trim( sprintf( "%7.3f", $delta * 1000.0 ) ), $space, $fname ); } function getTime() { @@ -316,8 +318,8 @@ class Profiler { $percent = $total ? 100. * $elapsed / $total : 0; $memory = $this->mMemory[$fname]; $prof .= sprintf($format, substr($fname, 0, $nameWidth), $calls, (float) ($elapsed * 1000), (float) ($elapsed * 1000) / $calls, $percent, $memory, ($this->mMin[$fname] * 1000.0), ($this->mMax[$fname] * 1000.0), $this->mOverhead[$fname]); - - if( $wgProfileToDatabase ){ + # Log to the DB + if( $wgProfileToDatabase ) { self::logToDB($fname, (float) ($elapsed * 1000), $calls, (float) ($memory) ); } } diff --git a/includes/ProfilerSimple.php b/includes/ProfilerSimple.php index 349a7cac..5989061d 100644 --- a/includes/ProfilerSimple.php +++ b/includes/ProfilerSimple.php @@ -4,7 +4,9 @@ * @ingroup Profiler */ -require_once(dirname(__FILE__).'/Profiler.php'); +if ( !class_exists( 'Profiler' ) ) { + require_once(dirname(__FILE__).'/Profiler.php'); +} /** * Simple profiler base class. diff --git a/includes/ProfilerSimpleTrace.php b/includes/ProfilerSimpleTrace.php new file mode 100644 index 00000000..63119228 --- /dev/null +++ b/includes/ProfilerSimpleTrace.php @@ -0,0 +1,73 @@ +<?php +/** + * @file + * @ingroup Profiler + */ + +if ( !class_exists( 'ProfilerSimple' ) ) { + require_once(dirname(__FILE__).'/ProfilerSimple.php'); +} + +/** + * Execution trace + * @todo document methods (?) + * @ingroup Profiler + */ +class ProfilerSimpleTrace extends ProfilerSimple { + var $mMinimumTime = 0; + var $mProfileID = false; + var $trace = ""; + var $memory = 0; + + function __construct() { + global $wgRequestTime, $wgRUstart; + if (!empty($wgRequestTime) && !empty($wgRUstart)) { + $this->mWorkStack[] = array( '-total', 0, $wgRequestTime,$this->getCpuTime($wgRUstart)); + $elapsedcpu = $this->getCpuTime() - $this->getCpuTime($wgRUstart); + $elapsedreal = microtime(true) - $wgRequestTime; + } + $this->trace .= "Beginning trace: \n"; + } + + function profileIn($functionname) { + global $wgDebugFunctionEntry; + $this->mWorkStack[] = array($functionname, count( $this->mWorkStack ), microtime(true), $this->getCpuTime()); + $this->trace .= " " . sprintf("%6.1f",$this->memoryDiff()) . str_repeat( " ", count($this->mWorkStack)) . " > " . $functionname . "\n"; + } + + function profileOut($functionname) { + global $wgDebugFunctionEntry; + + if ($wgDebugFunctionEntry) { + $this->debug(str_repeat(' ', count($this->mWorkStack) - 1).'Exiting '.$functionname."\n"); + } + + list($ofname, /* $ocount */ ,$ortime,$octime) = array_pop($this->mWorkStack); + + if (!$ofname) { + $this->trace .= "Profiling error: $functionname\n"; + } else { + if ($functionname == 'close') { + $message = "Profile section ended by close(): {$ofname}"; + $functionname = $ofname; + $this->trace .= $message . "\n"; + } + elseif ($ofname != $functionname) { + $self->trace .= "Profiling error: in({$ofname}), out($functionname)"; + } + $elapsedcpu = $this->getCpuTime() - $octime; + $elapsedreal = microtime(true) - $ortime; + $this->trace .= sprintf("%03.6f %6.1f",$elapsedreal,$this->memoryDiff()) . str_repeat(" ",count($this->mWorkStack)+1) . " < " . $functionname . "\n"; + } + } + + function memoryDiff() { + $diff = memory_get_usage() - $this->memory; + $this->memory = memory_get_usage(); + return $diff/1024; + } + + function getOutput() { + print "<!-- \n {$this->trace} \n -->"; + } +} diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index 372edfcd..5fe3cbc7 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -235,7 +235,7 @@ class ProtectionForm { $reasonstr = $this->mReasonSelection; if ( $reasonstr != 'other' && $this->mReason != '' ) { // Entry from drop down menu + additional comment - $reasonstr .= ': ' . $this->mReason; + $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->mReason; } elseif ( $reasonstr == 'other' ) { $reasonstr = $this->mReason; } @@ -374,7 +374,8 @@ class ProtectionForm { </tr></table>"; } # Add custom expiry field - $attribs = array( 'id' => "mwProtect-$action-expires", 'onkeyup' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib; + $attribs = array( 'id' => "mwProtect-$action-expires", + 'onkeyup' => 'ProtectionForm.updateExpiry(this)' ) + $this->disabledAttrib; $out .= "<table><tr> <td class='mw-label'>" . $mProtectother . diff --git a/includes/QueryPage.php b/includes/QueryPage.php index 0b587508..1cef31ea 100644 --- a/includes/QueryPage.php +++ b/includes/QueryPage.php @@ -204,7 +204,7 @@ class QueryPage { * Clear the cache and save new results */ function recache( $limit, $ignoreErrors = true ) { - $fname = get_class($this) . '::recache'; + $fname = get_class( $this ) . '::recache'; $dbw = wfGetDB( DB_MASTER ); $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) ); if ( !$dbw || !$dbr ) { @@ -222,9 +222,9 @@ class QueryPage { $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); # Do query $sql = $this->getSQL() . $this->getOrder(); - if ($limit !== false) - $sql = $dbr->limitResult($sql, $limit, 0); - $res = $dbr->query($sql, $fname); + if ( $limit !== false ) + $sql = $dbr->limitResult( $sql, $limit, 0 ); + $res = $dbr->query( $sql, $fname ); $num = false; if ( $res ) { $num = $dbr->numRows( $res ); @@ -238,7 +238,7 @@ class QueryPage { $insertSql .= ','; } if ( isset( $row->value ) ) { - $value = $row->value; + $value = intval( $row->value ); // @bug 14414 } else { $value = 0; } diff --git a/includes/RawPage.php b/includes/RawPage.php index 7093367f..b422d49e 100644 --- a/includes/RawPage.php +++ b/includes/RawPage.php @@ -127,6 +127,15 @@ class RawPage { $url = $_SERVER['PHP_SELF']; } + if( $url == '' ) { + # This will make the next check fail with a confusing error + # message, so we should mention it separately. + wfHttpError( 500, 'Internal Server Error', + "\$_SERVER['PHP_SELF'] is not set. Perhaps you're using CGI" . + " and haven't set cgi.fix_pathinfo = 1 in php.ini?" ); + return; + } + if( strcmp( $wgScript, $url ) ) { # Internet Explorer will ignore the Content-Type header if it # thinks it sees a file extension it recognizes. Make sure that @@ -164,7 +173,7 @@ class RawPage { $text = $this->getRawText(); if( !wfRunHooks( 'RawPageViewBeforeOutput', array( &$this, &$text ) ) ) { - wfDebug( __METHOD__ . ': RawPageViewBeforeOutput hook broke raw page output.' ); + wfDebug( __METHOD__ . ": RawPageViewBeforeOutput hook broke raw page output.\n" ); } echo $text; diff --git a/includes/RecentChange.php b/includes/RecentChange.php index f03fbcbb..8e3f1107 100644 --- a/includes/RecentChange.php +++ b/includes/RecentChange.php @@ -166,6 +166,9 @@ class RecentChange # Set the ID $this->mAttribs['rc_id'] = $dbw->insertId(); + + # Notify extensions + wfRunHooks( 'RecentChange_save', array( &$this ) ); # Notify external application via UDP if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) { @@ -193,9 +196,14 @@ class RecentChange $this->mAttribs['rc_minor'], $this->mAttribs['rc_last_oldid'] ); } - - # Notify extensions - wfRunHooks( 'RecentChange_save', array( &$this ) ); + } + + public function notifyRC2UDP() { + global $wgRC2UDPAddress, $wgRC2UDPOmitBots; + # Notify external application via UDP + if( $wgRC2UDPAddress && ( !$this->mAttribs['rc_bot'] || !$wgRC2UDPOmitBots ) ) { + self::sendToUDP( $this->getIRCLine() ); + } } /** @@ -227,12 +235,12 @@ class RecentChange } /** - * Remove newlines and carriage returns + * Remove newlines, carriage returns and decode html entites * @param string $line * @return string */ public static function cleanupForIRC( $text ) { - return str_replace(array("\n", "\r"), array("", ""), $text); + return Sanitizer::decodeCharReferences( str_replace( array( "\n", "\r" ), array( "", "" ), $text ) ); } /** @@ -318,9 +326,7 @@ class RecentChange { if( !$ip ) { $ip = wfGetIP(); - if( !$ip ) { - $ip = ''; - } + if( !$ip ) $ip = ''; } $rc = new RecentChange; @@ -372,9 +378,7 @@ class RecentChange { if( !$ip ) { $ip = wfGetIP(); - if( !$ip ) { - $ip = ''; - } + if( !$ip ) $ip = ''; } $rc = new RecentChange; @@ -420,12 +424,9 @@ class RecentChange public static function notifyMove( $timestamp, &$oldTitle, &$newTitle, &$user, $comment, $ip='', $overRedir = false ) { global $wgRequest; - if( !$ip ) { $ip = wfGetIP(); - if( !$ip ) { - $ip = ''; - } + if( !$ip ) $ip = ''; } $rc = new RecentChange; @@ -473,16 +474,27 @@ class RecentChange RecentChange::notifyMove( $timestamp, $oldTitle, $newTitle, $user, $comment, $ip, true ); } - public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='', - $type, $action, $target, $logComment, $params, $newId=0 ) + public static function notifyLog( $timestamp, &$title, &$user, $actionComment, $ip='', $type, + $action, $target, $logComment, $params, $newId=0 ) { - global $wgRequest; + global $wgLogRestrictions; + # Don't add private logs to RC! + if( isset($wgLogRestrictions[$type]) && $wgLogRestrictions[$type] != '*' ) { + return false; + } + $rc = self::newLogEntry( $timestamp, $title, $user, $actionComment, $ip, $type, $action, + $target, $logComment, $params, $newId ); + $rc->save(); + return true; + } + public static function newLogEntry( $timestamp, &$title, &$user, $actionComment, $ip='', + $type, $action, $target, $logComment, $params, $newId=0 ) + { + global $wgRequest; if( !$ip ) { $ip = wfGetIP(); - if( !$ip ) { - $ip = ''; - } + if( !$ip ) $ip = ''; } $rc = new RecentChange; @@ -518,7 +530,7 @@ class RecentChange 'lastTimestamp' => 0, 'actionComment' => $actionComment, // the comment appended to the action, passed from LogPage ); - $rc->save(); + return $rc; } # Initialises the members of this object from a mysql row object @@ -589,7 +601,7 @@ class RecentChange return $trail; } - protected function getIRCLine() { + public function getIRCLine() { global $wgUseRCPatrol, $wgUseNPPatrol, $wgRC2UDPInterwikiPrefix, $wgLocalInterwiki; // FIXME: Would be good to replace these 2 extract() calls with something more explicit @@ -643,7 +655,11 @@ class RecentChange $flag = $rc_log_action; } else { $comment = self::cleanupForIRC( $rc_comment ); - $flag = ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : ""); + $flag = ''; + if( !$rc_patrolled && ($wgUseRCPatrol || $rc_new && $wgUseNPPatrol) ) { + $flag .= '!'; + } + $flag .= ($rc_new ? "N" : "") . ($rc_minor ? "M" : "") . ($rc_bot ? "B" : ""); } if ( $wgRC2UDPInterwikiPrefix === true ) { diff --git a/includes/RefreshLinksJob.php b/includes/RefreshLinksJob.php index 1c119a8d..91cff40b 100644 --- a/includes/RefreshLinksJob.php +++ b/includes/RefreshLinksJob.php @@ -82,35 +82,21 @@ class RefreshLinksJob2 extends Job { wfProfileOut( __METHOD__ ); return false; } - $start = intval($this->params['start']); - $end = intval($this->params['end']); - - $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array( 'templatelinks', 'page' ), - array( 'page_namespace', 'page_title' ), - array( - 'page_id=tl_from', - "tl_from >= '$start'", - "tl_from <= '$end'", - 'tl_namespace' => $this->title->getNamespace(), - 'tl_title' => $this->title->getDBkey() - ), __METHOD__ - ); + $titles = $this->title->getBacklinkCache()->getLinks( + 'templatelinks', $this->params['start'], $this->params['end']); # Not suitable for page load triggered job running! # Gracefully switch to refreshLinks jobs if this happens. if( php_sapi_name() != 'cli' ) { $jobs = array(); - while( $row = $dbr->fetchObject( $res ) ) { - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + foreach ( $titles as $title ) { $jobs[] = new RefreshLinksJob( $title, '' ); } Job::batchInsert( $jobs ); return true; } # Re-parse each page that transcludes this page and update their tracking links... - while( $row = $dbr->fetchObject( $res ) ) { - $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + foreach ( $titles as $title ) { $revision = Revision::newFromTitle( $title ); if ( !$revision ) { $this->error = 'refreshLinks: Article not found "' . $title->getPrefixedDBkey() . '"'; diff --git a/includes/Revision.php b/includes/Revision.php index 7938d88a..8a2149c0 100644 --- a/includes/Revision.php +++ b/includes/Revision.php @@ -53,6 +53,10 @@ class Revision { // Get the latest revision ID from the master $dbw = wfGetDB( DB_MASTER ); $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ ); + if ( $latest === false ) { + // Page does not exist + return null; + } $conds['rev_id'] = $latest; } else { // Use a join to get the latest revision @@ -363,6 +367,7 @@ class Revision { } else { throw new MWException( 'Revision constructor passed invalid row format.' ); } + $this->mUnpatrolled = NULL; } /**#@+ @@ -536,6 +541,27 @@ class Revision { public function isMinor() { return (bool)$this->mMinorEdit; } + + /** + * @return int rcid of the unpatrolled row, zero if there isn't one + */ + public function isUnpatrolled() { + if( $this->mUnpatrolled !== NULL ) { + return $this->mUnpatrolled; + } + $dbr = wfGetDB( DB_SLAVE ); + $this->mUnpatrolled = $dbr->selectField( 'recentchanges', + 'rc_id', + array( // Add redundant user,timestamp condition so we can use the existing index + 'rc_user_text' => $this->getRawUserText(), + 'rc_timestamp' => $dbr->timestamp( $this->getTimestamp() ), + 'rc_this_oldid' => $this->getId(), + 'rc_patrolled' => 0 + ), + __METHOD__ + ); + return (int)$this->mUnpatrolled; + } /** * int $field one of DELETED_* bitfield constants @@ -819,7 +845,8 @@ class Revision { 'rev_timestamp' => $dbw->timestamp( $this->mTimestamp ), 'rev_deleted' => $this->mDeleted, 'rev_len' => $this->mSize, - 'rev_parent_id' => $this->mParentId ? $this->mParentId : $this->getPreviousRevisionId( $dbw ) + 'rev_parent_id' => is_null($this->mParentId) ? + $this->getPreviousRevisionId( $dbw ) : $this->mParentId ), __METHOD__ ); @@ -961,6 +988,10 @@ class Revision { */ static function getTimestampFromId( $title, $id ) { $dbr = wfGetDB( DB_SLAVE ); + // Casting fix for DB2 + if ($id == '') { + $id = 0; + } $conds = array( 'rev_id' => $id ); $conds['rev_page'] = $title->getArticleId(); $timestamp = $dbr->selectField( 'revision', 'rev_timestamp', $conds, __METHOD__ ); diff --git a/includes/SearchEngine.php b/includes/SearchEngine.php index 3ea0341d..e5392f7c 100644 --- a/includes/SearchEngine.php +++ b/includes/SearchEngine.php @@ -80,8 +80,13 @@ class SearchEngine { if (is_null($title)) return NULL; - if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal() - || $title->exists() ) { + if ( $title->getNamespace() == NS_SPECIAL || $title->isExternal() || $title->exists() ) { + return $title; + } + + # See if it still otherwise has content is some sane sense + $article = MediaWiki::articleFromTitle( $title ); + if( $article->hasViewableContent() ) { return $title; } @@ -403,7 +408,7 @@ class SearchEngine { if($wgMWSuggestTemplate) return $wgMWSuggestTemplate; else - return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}'; + return $wgServer . $wgScriptPath . '/api.php?action=opensearch&search={searchTerms}&namespace={namespaces}&suggest'; } } @@ -1165,12 +1170,12 @@ class SearchHighlighter { continue; } --$contextlines; - $pre = $wgContLang->truncate( $m[1], -$contextchars, ' ... ' ); + $pre = $wgContLang->truncate( $m[1], -$contextchars ); if ( count( $m ) < 3 ) { $post = ''; } else { - $post = $wgContLang->truncate( $m[3], $contextchars, ' ... ' ); + $post = $wgContLang->truncate( $m[3], $contextchars ); } $found = $m[2]; diff --git a/includes/SearchIBM_DB2.php b/includes/SearchIBM_DB2.php new file mode 100644 index 00000000..57813a73 --- /dev/null +++ b/includes/SearchIBM_DB2.php @@ -0,0 +1,247 @@ +<?php +# Copyright (C) 2004 Brion Vibber <brion@pobox.com> +# http://www.mediawiki.org/ +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html + +/** + * @file + * @ingroup Search + */ + +/** + * Search engine hook base class for IBM DB2 + * @ingroup Search + */ +class SearchIBM_DB2 extends SearchEngine { + function __construct($db) { + $this->db = $db; + } + + /** + * Perform a full text search query and return a result set. + * + * @param string $term - Raw search term + * @return IBM_DB2SearchResultSet + * @access public + */ + function searchText( $term ) { + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), true))); + return new IBM_DB2SearchResultSet($resultSet, $this->searchTerms); + } + + /** + * Perform a title-only search query and return a result set. + * + * @param string $term - Raw search term + * @return IBM_DB2SearchResultSet + * @access public + */ + function searchTitle($term) { + $resultSet = $this->db->resultObject($this->db->query($this->getQuery($this->filter($term), false))); + return new MySQLSearchResultSet($resultSet, $this->searchTerms); + } + + + /** + * Return a partial WHERE clause to exclude redirects, if so set + * @return string + * @private + */ + function queryRedirect() { + if ($this->showRedirects) { + return ''; + } else { + return 'AND page_is_redirect=0'; + } + } + + /** + * Return a partial WHERE clause to limit the search to the given namespaces + * @return string + * @private + */ + function queryNamespaces() { + if( is_null($this->namespaces) ) + return ''; + $namespaces = implode(',', $this->namespaces); + if ($namespaces == '') { + $namespaces = '0'; + } + return 'AND page_namespace IN (' . $namespaces . ')'; + } + + /** + * Return a LIMIT clause to limit results on the query. + * @return string + * @private + */ + function queryLimit($sql) { + return $this->db->limitResult($sql, $this->limit, $this->offset); + } + + /** + * Does not do anything for generic search engine + * subclasses may define this though + * @return string + * @private + */ + function queryRanking($filteredTerm, $fulltext) { + // requires Net Search Extender or equivalent + // return ' ORDER BY score(1)'; + return ''; + } + + /** + * Construct the full SQL query to do the search. + * The guts shoulds be constructed in queryMain() + * @param string $filteredTerm + * @param bool $fulltext + * @private + */ + function getQuery( $filteredTerm, $fulltext ) { + return $this->queryLimit($this->queryMain($filteredTerm, $fulltext) . ' ' . + $this->queryRedirect() . ' ' . + $this->queryNamespaces() . ' ' . + $this->queryRanking( $filteredTerm, $fulltext ) . ' '); + } + + + /** + * Picks which field to index on, depending on what type of query. + * @param bool $fulltext + * @return string + */ + function getIndexField($fulltext) { + return $fulltext ? 'si_text' : 'si_title'; + } + + /** + * Get the base part of the search query. + * + * @param string $filteredTerm + * @param bool $fulltext + * @return string + * @private + */ + function queryMain( $filteredTerm, $fulltext ) { + $match = $this->parseQuery($filteredTerm, $fulltext); + $page = $this->db->tableName('page'); + $searchindex = $this->db->tableName('searchindex'); + return 'SELECT page_id, page_namespace, page_title ' . + "FROM $page,$searchindex " . + 'WHERE page_id=si_page AND ' . $match; + } + + /** @todo document */ + function parseQuery($filteredText, $fulltext) { + global $wgContLang; + $lc = SearchEngine::legalSearchChars(); + $this->searchTerms = array(); + + # FIXME: This doesn't handle parenthetical expressions. + $m = array(); + $q = array(); + + if (preg_match_all('/([-+<>~]?)(([' . $lc . ']+)(\*?)|"[^"]*")/', + $filteredText, $m, PREG_SET_ORDER)) { + foreach($m as $terms) { + $q[] = $terms[1] . $wgContLang->stripForSearch($terms[2]); + + if (!empty($terms[3])) { + $regexp = preg_quote( $terms[3], '/' ); + if ($terms[4]) + $regexp .= "[0-9A-Za-z_]+"; + } else { + $regexp = preg_quote(str_replace('"', '', $terms[2]), '/'); + } + $this->searchTerms[] = $regexp; + } + } + + $searchon = $this->db->strencode(join(',', $q)); + $field = $this->getIndexField($fulltext); + + // requires Net Search Extender or equivalent + //return " CONTAINS($field, '$searchon') > 0 "; + + return " lcase($field) LIKE lcase('%$searchon%')"; + } + + /** + * Create or update the search index record for the given page. + * Title and text should be pre-processed. + * + * @param int $id + * @param string $title + * @param string $text + */ + function update($id, $title, $text) { + $dbw = wfGetDB(DB_MASTER); + $dbw->replace('searchindex', + array('si_page'), + array( + 'si_page' => $id, + 'si_title' => $title, + 'si_text' => $text + ), 'SearchIBM_DB2::update' ); + // ? + //$dbw->query("CALL ctx_ddl.sync_index('si_text_idx')"); + //$dbw->query("CALL ctx_ddl.sync_index('si_title_idx')"); + } + + /** + * Update a search index record's title only. + * Title should be pre-processed. + * + * @param int $id + * @param string $title + */ + function updateTitle($id, $title) { + $dbw = wfGetDB(DB_MASTER); + + $dbw->update('searchindex', + array('si_title' => $title), + array('si_page' => $id), + 'SearchIBM_DB2::updateTitle', + array()); + } +} + +/** + * @ingroup Search + */ +class IBM_DB2SearchResultSet extends SearchResultSet { + function __construct($resultSet, $terms) { + $this->mResultSet = $resultSet; + $this->mTerms = $terms; + } + + function termMatches() { + return $this->mTerms; + } + + function numRows() { + return $this->mResultSet->numRows(); + } + + function next() { + $row = $this->mResultSet->fetchObject(); + if ($row === false) + return false; + return new SearchResult($row); + } +} diff --git a/includes/SearchPostgres.php b/includes/SearchPostgres.php index 4862a44e..fa9d8420 100644 --- a/includes/SearchPostgres.php +++ b/includes/SearchPostgres.php @@ -70,7 +70,7 @@ class SearchPostgres extends SearchEngine { */ function parseQuery( $term ) { - wfDebug( "parseQuery received: $term" ); + wfDebug( "parseQuery received: $term \n" ); ## No backslashes allowed $term = preg_replace('/\\\/', '', $term); @@ -122,7 +122,7 @@ class SearchPostgres extends SearchEngine { ## Quote the whole thing $searchstring = $this->db->addQuotes($searchstring); - wfDebug( "parseQuery returned: $searchstring" ); + wfDebug( "parseQuery returned: $searchstring \n" ); return $searchstring; @@ -194,7 +194,7 @@ class SearchPostgres extends SearchEngine { $query .= $this->db->limitResult( '', $this->limit, $this->offset ); - wfDebug( "searchQuery returned: $query" ); + wfDebug( "searchQuery returned: $query \n" ); return $query; } diff --git a/includes/Setup.php b/includes/Setup.php index 859ad008..d450dfdb 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -197,6 +197,10 @@ if($wgMetaNamespace === FALSE) { # To determine the user language, use $wgLang->getCode() $wgContLanguageCode = $wgLanguageCode; +# Easy to forget to falsify $wgShowIPinHeader for static caches. +# If file cache or squid cache is on, just disable this (DWIMD). +if( $wgUseFileCache || $wgUseSquid ) $wgShowIPinHeader = false; + wfProfileOut( $fname.'-misc1' ); wfProfileIn( $fname.'-memcached' ); diff --git a/includes/SiteStats.php b/includes/SiteStats.php index ab0caa7e..9427536f 100644 --- a/includes/SiteStats.php +++ b/includes/SiteStats.php @@ -222,7 +222,7 @@ class SiteStatsUpdate { if ( $updates ) { $site_stats = $dbw->tableName( 'site_stats' ); - $sql = $dbw->limitResultForUpdate("UPDATE $site_stats SET $updates", 1); + $sql = "UPDATE $site_stats SET $updates"; # Need a separate transaction because this a global lock $dbw->begin(); @@ -240,7 +240,7 @@ class SiteStatsUpdate { __METHOD__ ); $dbw->update( 'site_stats', array( 'ss_active_users' => intval($activeUsers) ), - array( 'ss_row_id' => 1 ), __METHOD__, array( 'LIMIT' => 1 ) + array( 'ss_row_id' => 1 ), __METHOD__ ); } } diff --git a/includes/Skin.php b/includes/Skin.php index 636b96bf..47285acc 100644 --- a/includes/Skin.php +++ b/includes/Skin.php @@ -401,7 +401,7 @@ class Skin extends Linker { $vars['wgLivepreviewMessageError'] = wfMsg( 'livepreview-error' ); } - if($wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) { + if ( $wgOut->isArticleRelated() && $wgUseAjax && $wgAjaxWatch && $wgUser->isLoggedIn() ) { $msgs = (object)array(); foreach ( array( 'watch', 'unwatch', 'watching', 'unwatching' ) as $msgName ) { $msgs->{$msgName . 'Msg'} = wfMsg( $msgName ); @@ -758,7 +758,7 @@ END; if( count( $wgOut->mCategoryLinks ) == 0 ) return ''; # Separator - $sep = wfMsgHtml( 'catseparator' ); + $sep = wfMsgExt( 'catseparator', array( 'parsemag', 'escapenoentities' ) ); // Use Unicode bidi embedding override characters, // to make sure links don't smash each other up in ugly ways. @@ -890,12 +890,27 @@ END; } /** + * Generate debug data HTML for displaying at the bottom of the main content + * area. + * @return String HTML containing debug data, if enabled (otherwise empty). + */ + protected function generateDebugHTML() { + global $wgShowDebug, $wgOut; + if ( $wgShowDebug ) { + $listInternals = str_replace( "\n", "</li>\n<li>", htmlspecialchars( $wgOut->mDebugtext ) ); + return "\n<hr>\n<strong>Debug data:</strong><ul style=\"font-family:monospace;\"><li>" . + $listInternals . "</li></ul>\n"; + } + return ''; + } + + /** * This gets called shortly before the </body> tag. * @return String HTML to be put before </body> */ function afterContent() { $printfooter = "<div class=\"printfooter\">\n" . $this->printFooter() . "</div>\n"; - return $printfooter . $this->doAfterContent(); + return $printfooter . $this->generateDebugHTML() . $this->doAfterContent(); } /** @@ -925,20 +940,20 @@ END; function doAfterContent() { return "</div></div>"; } function pageTitleLinks() { - global $wgOut, $wgTitle, $wgUser, $wgRequest; + global $wgOut, $wgTitle, $wgUser, $wgRequest, $wgLang; $oldid = $wgRequest->getVal( 'oldid' ); $diff = $wgRequest->getVal( 'diff' ); $action = $wgRequest->getText( 'action' ); - $s = $this->printableLink(); + $s[] = $this->printableLink(); $disclaimer = $this->disclaimerLink(); # may be empty if( $disclaimer ) { - $s .= ' | ' . $disclaimer; + $s[] = $disclaimer; } $privacy = $this->privacyLink(); # may be empty too if( $privacy ) { - $s .= ' | ' . $privacy; + $s[] = $privacy; } if ( $wgOut->isArticleRelated() ) { @@ -948,12 +963,12 @@ END; if( $image ) { $link = htmlspecialchars( $image->getURL() ); $style = $this->getInternalLinkAttributes( $link, $name ); - $s .= " | <a href=\"{$link}\"{$style}>{$name}</a>"; + $s[] = "<a href=\"{$link}\"{$style}>{$name}</a>"; } } } if ( 'history' == $action || isset( $diff ) || isset( $oldid ) ) { - $s .= ' | ' . $this->makeKnownLinkObj( $wgTitle, + $s[] .= $this->makeKnownLinkObj( $wgTitle, wfMsg( 'currentrev' ) ); } @@ -963,7 +978,7 @@ END; if( !$wgTitle->equals( $wgUser->getTalkPage() ) ) { $tl = $this->makeKnownLinkObj( $wgUser->getTalkPage(), wfMsgHtml( 'newmessageslink' ), 'redirect=no' ); $dl = $this->makeKnownLinkObj( $wgUser->getTalkPage(), wfMsgHtml( 'newmessagesdifflink' ), 'diff=cur' ); - $s.= ' | <strong>'. wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>'; + $s[] = '<strong>'. wfMsg( 'youhavenewmessages', $tl, $dl ) . '</strong>'; # disable caching $wgOut->setSquidMaxage(0); $wgOut->enableClientCache(false); @@ -972,9 +987,9 @@ END; $undelete = $this->getUndeleteLink(); if( !empty( $undelete ) ) { - $s .= ' | '.$undelete; + $s[] = $undelete; } - return $s; + return $wgLang->pipeList( $s ); } function getUndeleteLink() { @@ -997,18 +1012,19 @@ END; } function printableLink() { - global $wgOut, $wgFeedClasses, $wgRequest; + global $wgOut, $wgFeedClasses, $wgRequest, $wgLang; $printurl = $wgRequest->escapeAppendQuery( 'printable=yes' ); - $s = "<a href=\"$printurl\">" . wfMsg( 'printableversion' ) . '</a>'; + $s[] = "<a href=\"$printurl\" rel=\"alternate\">" . wfMsg( 'printableversion' ) . '</a>'; if( $wgOut->isSyndicated() ) { foreach( $wgFeedClasses as $format => $class ) { $feedurl = $wgRequest->escapeAppendQuery( "feed=$format" ); - $s .= " | <a href=\"$feedurl\">{$format}</a>"; + $s[] = "<a href=\"$feedurl\" rel=\"alternate\" type=\"application/{$format}+xml\"" + . " class=\"feedlink\">" . wfMsgHtml( "feed-$format" ) . "</a>"; } } - return $s; + return $wgLang->pipeList( $s ); } function pageTitle() { @@ -1053,7 +1069,7 @@ END; $getlink = $this->makeKnownLinkObj( $linkObj, htmlspecialchars( $display ) ); $c++; if ($c>1) { - $subpages .= ' | '; + $subpages .= wfMsgExt( 'pipe-separator' , 'escapenoentities' ); } else { $subpages .= '< '; } @@ -1116,16 +1132,21 @@ END; $ret .= $this->link( $wgUser->getUserPage(), htmlspecialchars( $wgUser->getName() ) ); $ret .= " ($talkLink)<br />"; - $ret .= $this->link( - SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ), - array(), array( 'returnto' => $returnTo ) - ); - $ret .= ' | ' . $this->specialLink( 'preferences' ); - } - $ret .= ' | ' . $this->link( - Title::newFromText( wfMsgForContent( 'helppage' ) ), - wfMsg( 'help' ) - ); + $ret .= $wgLang->pipeList( array( + $this->link( + SpecialPage::getTitleFor( 'Userlogout' ), wfMsg( 'logout' ), + array(), array( 'returnto' => $returnTo ) + ), + $this->specialLink( 'preferences' ), + ) ); + } + $ret = $wgLang->pipeList( array( + $ret, + $this->link( + Title::newFromText( wfMsgForContent( 'helppage' ) ), + wfMsg( 'help' ) + ), + ) ); return $ret; } @@ -1140,16 +1161,22 @@ END; } function searchForm() { - global $wgRequest; + global $wgRequest, $wgUseTwoButtonsSearchForm; $search = $wgRequest->getText( 'search' ); $s = '<form id="searchform'.$this->searchboxes.'" name="search" class="inline" method="post" action="' . $this->escapeSearchLink() . "\">\n" . '<input type="text" id="searchInput'.$this->searchboxes.'" name="search" size="19" value="' . htmlspecialchars(substr($search,0,256)) . "\" />\n" - . '<input type="submit" name="go" value="' . wfMsg ('searcharticle') . '" /> ' - . '<input type="submit" name="fulltext" value="' . wfMsg ('searchbutton') . "\" />\n</form>"; - + . '<input type="submit" name="go" value="' . wfMsg ('searcharticle') . '" />'; + + if ($wgUseTwoButtonsSearchForm) + $s .= ' <input type="submit" name="fulltext" value="' . wfMsg ('searchbutton') . "\" />\n"; + else + $s .= ' <a href="' . $this->escapeSearchLink() . '" rel="search">' . wfMsg ('powersearch-legend') . "</a>\n"; + + $s .= '</form>'; + // Ensure unique id's for search boxes made after the first $this->searchboxes = $this->searchboxes == '' ? 2 : $this->searchboxes + 1; @@ -1158,23 +1185,29 @@ END; function topLinks() { global $wgOut; - $sep = " |\n"; - $s = $this->mainPageLink() . $sep - . $this->specialLink( 'recentchanges' ); + $s = array( + $this->mainPageLink(), + $this->specialLink( 'recentchanges' ) + ); if ( $wgOut->isArticleRelated() ) { - $s .= $sep . $this->editThisPage() - . $sep . $this->historyLink(); + $s[] = $this->editThisPage(); + $s[] = $this->historyLink(); } # Many people don't like this dropdown box - #$s .= $sep . $this->specialPagesList(); + #$s[] = $this->specialPagesList(); - $s .= $this->variantLinks(); + if( $this->variantLinks() ) { + $s[] = $this->variantLinks(); + } - $s .= $this->extensionTabLinks(); + if( $this->extensionTabLinks() ) { + $s[] = $this->extensionTabLinks(); + } - return $s; + // FIXME: Is using Language::pipeList impossible here? Do not quite understand the use of the newline + return implode( $s, wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "\n" ); } /** @@ -1185,14 +1218,23 @@ END; */ function extensionTabLinks() { $tabs = array(); - $s = ''; + $out = ''; + $s = array(); wfRunHooks( 'SkinTemplateTabs', array( $this, &$tabs ) ); foreach( $tabs as $tab ) { - $s .= ' | ' . Xml::element( 'a', + $s[] = Xml::element( 'a', array( 'href' => $tab['href'] ), $tab['text'] ); } - return $s; + + if( count( $s ) ) { + global $wgLang; + + $out = wfMsgExt( 'pipe-separator' , 'escapenoentities' ); + $out .= $wgLang->pipeList( $s ); + } + + return $out; } /** @@ -1202,14 +1244,14 @@ END; function variantLinks() { $s = ''; /* show links to different language variants */ - global $wgDisableLangConversion, $wgContLang, $wgTitle; + global $wgDisableLangConversion, $wgLang, $wgContLang, $wgTitle; $variants = $wgContLang->getVariants(); if( !$wgDisableLangConversion && sizeof( $variants ) > 1 ) { foreach( $variants as $code ) { $varname = $wgContLang->getVariantname( $code ); if( $varname == 'disable' ) continue; - $s .= ' | <a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>'; + $s = $wgLang->pipeList( array( $s, '<a href="' . $wgTitle->escapeLocalUrl( 'variant=' . $code ) . '">' . htmlspecialchars( $varname ) . '</a>' ) ); } } return $s; @@ -1217,21 +1259,21 @@ END; function bottomLinks() { global $wgOut, $wgUser, $wgTitle, $wgUseTrackbacks; - $sep = " |\n"; + $sep = wfMsgExt( 'pipe-separator' , 'escapenoentities' ) . "\n"; $s = ''; if ( $wgOut->isArticleRelated() ) { - $s .= '<strong>' . $this->editThisPage() . '</strong>'; + $element[] = '<strong>' . $this->editThisPage() . '</strong>'; if ( $wgUser->isLoggedIn() ) { - $s .= $sep . $this->watchThisPage(); + $element[] = $this->watchThisPage(); } - $s .= $sep . $this->talkLink() - . $sep . $this->historyLink() - . $sep . $this->whatLinksHere() - . $sep . $this->watchPageLinksLink(); + $element[] = $this->talkLink(); + $element[] = $this->historyLink(); + $element[] = $this->whatLinksHere(); + $element[] = $this->watchPageLinksLink(); if ($wgUseTrackbacks) - $s .= $sep . $this->trackbackLink(); + $element[] = $this->trackbackLink(); if ( $wgTitle->getNamespace() == NS_USER || $wgTitle->getNamespace() == NS_USER_TALK ) @@ -1241,12 +1283,15 @@ END; $ip=User::isIP($wgTitle->getText()); if($id || $ip) { # both anons and non-anons have contri list - $s .= $sep . $this->userContribsLink(); + $element[] = $this->userContribsLink(); } if( $this->showEmailUser( $id ) ) { - $s .= $sep . $this->emailUserLink(); + $element[] = $this->emailUserLink(); } } + + $s = implode( $element, $sep ); + if ( $wgTitle->getArticleId() ) { $s .= "\n<br />"; if($wgUser->isAllowed('delete')) { $s .= $this->deleteThisPage(); } @@ -1255,6 +1300,7 @@ END; } $s .= "<br />\n" . $this->otherLanguages(); } + return $s; } @@ -1566,8 +1612,8 @@ END; function historyLink() { global $wgTitle; - return $this->makeKnownLinkObj( $wgTitle, - wfMsg( 'history' ), 'action=history' ); + return $this->link( $wgTitle, wfMsg( 'history' ), + array( 'rel' => 'archives' ), array( 'action' => 'history' ) ); } function whatLinksHere() { @@ -1632,11 +1678,11 @@ END; return ''; } - $s = wfMsg( 'otherlanguages' ) . ': '; + $s = wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' ); $first = true; if($wgContLang->isRTL()) $s .= '<span dir="LTR">'; foreach( $a as $l ) { - if ( ! $first ) { $s .= ' | '; } + if ( ! $first ) { $s .= wfMsgExt( 'pipe-separator' , 'escapenoentities' ); } $first = false; $nt = Title::newFromText( $l ); diff --git a/includes/SkinTemplate.php b/includes/SkinTemplate.php index 4f13571a..4317a93e 100644 --- a/includes/SkinTemplate.php +++ b/includes/SkinTemplate.php @@ -138,7 +138,7 @@ class SkinTemplate extends Skin { global $wgScript, $wgStylePath, $wgContLanguageCode; global $wgMimeType, $wgJsMimeType, $wgOutputEncoding, $wgRequest; global $wgXhtmlDefaultNamespace, $wgXhtmlNamespaces; - global $wgDisableCounters, $wgLogo, $action, $wgFeedClasses, $wgHideInterlanguageLinks; + global $wgDisableCounters, $wgLogo, $wgHideInterlanguageLinks; global $wgMaxCredits, $wgShowCreditsIfMax; global $wgPageShowWatchingUsers; global $wgUseTrackbacks, $wgUseSiteJs; @@ -148,6 +148,7 @@ class SkinTemplate extends Skin { $oldid = $wgRequest->getVal( 'oldid' ); $diff = $wgRequest->getVal( 'diff' ); + $action = $wgRequest->getVal( 'action', 'view' ); wfProfileIn( __METHOD__."-init" ); $this->initPage( $out ); @@ -258,6 +259,7 @@ class SkinTemplate extends Skin { $tpl->set( "helppage", wfMsg('helppage')); */ $tpl->set( 'searchaction', $this->escapeSearchLink() ); + $tpl->set( 'searchtitle', SpecialPage::getTitleFor('search')->getPrefixedDBKey() ); $tpl->set( 'search', trim( $wgRequest->getVal( 'search' ) ) ); $tpl->setRef( 'stylepath', $wgStylePath ); $tpl->setRef( 'articlepath', $wgArticlePath ); @@ -401,7 +403,7 @@ class SkinTemplate extends Skin { $tpl->set( 'bottomscripts', $this->bottomScripts() ); $printfooter = "<div class=\"printfooter\">\n" . $this->printSource() . "</div>\n"; - $out->mBodytext .= $printfooter ; + $out->mBodytext .= $printfooter . $this->generateDebugHTML(); $tpl->setRef( 'bodytext', $out->mBodytext ); # Language links @@ -449,7 +451,7 @@ class SkinTemplate extends Skin { // original version by hansm if( !wfRunHooks( 'SkinTemplateOutputPageBeforeExec', array( &$this, &$tpl ) ) ) { - wfDebug( __METHOD__ . ': Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!' ); + wfDebug( __METHOD__ . ": Hook SkinTemplateOutputPageBeforeExec broke outputPage execution!\n" ); } // allow extensions adding stuff after the page content. @@ -647,12 +649,12 @@ class SkinTemplate extends Skin { * @private */ function buildContentActionUrls() { - global $wgContLang, $wgLang, $wgOut; + global $wgContLang, $wgLang, $wgOut, $wgUser, $wgRequest; + wfProfileIn( __METHOD__ ); - global $wgUser, $wgRequest; - $action = $wgRequest->getText( 'action' ); - $section = $wgRequest->getText( 'section' ); + $action = $wgRequest->getVal( 'action', 'view' ); + $section = $wgRequest->getVal( 'section' ); $content_actions = array(); $prevent_active_tabs = false ; @@ -689,11 +691,13 @@ class SkinTemplate extends Skin { ); if ( $istalk || $wgOut->showNewSectionLink() ) { - $content_actions['addsection'] = array( - 'class' => $section == 'new'?'selected':false, - 'text' => wfMsg('addsection'), - 'href' => $this->mTitle->getLocalUrl( 'action=edit§ion=new' ) - ); + if ( !$wgOut->forceHideNewSectionLink() ) { + $content_actions['addsection'] = array( + 'class' => $section == 'new' ? 'selected' : false, + 'text' => wfMsg('addsection'), + 'href' => $this->mTitle->getLocalUrl( 'action=edit§ion=new' ) + ); + } } } elseif ( $this->mTitle->isKnown() ) { $content_actions['viewsource'] = array( @@ -710,7 +714,8 @@ class SkinTemplate extends Skin { $content_actions['history'] = array( 'class' => ($action == 'history') ? 'selected' : false, 'text' => wfMsg('history_short'), - 'href' => $this->mTitle->getLocalUrl( 'action=history') + 'href' => $this->mTitle->getLocalUrl( 'action=history' ), + 'rel' => 'archives', ); if( $wgUser->isAllowed('delete') ) { @@ -848,7 +853,7 @@ class SkinTemplate extends Skin { wfProfileIn( __METHOD__ ); - $action = $wgRequest->getText( 'action' ); + $action = $wgRequest->getVal( 'action', 'view' ); $nav_urls = array(); $nav_urls['mainpage'] = array( 'href' => self::makeMainPageUrl() ); @@ -871,7 +876,7 @@ class SkinTemplate extends Skin { // A print stylesheet is attached to all pages, but nobody ever // figures that out. :) Add a link... - if( $this->iscontent && ($action == '' || $action == 'view' || $action == 'purge' ) ) { + if( $this->iscontent && ( $action == 'view' || $action == 'purge' ) ) { $nav_urls['print'] = array( 'text' => wfMsg( 'printableversion' ), 'href' => $wgRequest->appendQuery( 'printable=yes' ) @@ -965,10 +970,11 @@ class SkinTemplate extends Skin { * @private */ function setupUserJs( $allowUserJs ) { + global $wgRequest, $wgJsMimeType; + wfProfileIn( __METHOD__ ); - global $wgRequest, $wgJsMimeType; - $action = $wgRequest->getText('action'); + $action = $wgRequest->getVal( 'action', 'view' ); if( $allowUserJs && $this->loggedin ) { if( $this->mTitle->isJsSubpage() and $this->userCanPreview( $action ) ) { diff --git a/includes/SpecialPage.php b/includes/SpecialPage.php index 00eacd1e..31b43839 100644 --- a/includes/SpecialPage.php +++ b/includes/SpecialPage.php @@ -80,92 +80,112 @@ class SpecialPage ** array( 'SpecialRedirectToSpecial', name, page to redirect to, special page param, ... ) */ static public $mList = array( - 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ), + # Maintenance Reports 'BrokenRedirects' => array( 'SpecialPage', 'BrokenRedirects' ), + 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ), + 'DoubleRedirects' => array( 'SpecialPage', 'DoubleRedirects' ), + 'Longpages' => array( 'SpecialPage', 'Longpages' ), + 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ), + 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ), + 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ), + 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ), + 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ), + 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ), + 'Shortpages' => array( 'SpecialPage', 'Shortpages' ), + 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ), + 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ), + 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ), + 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ), + 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ), + 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ), + 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ), + 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ), + 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ), + 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ), + 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ), + 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ), + + # List of pages + 'Allpages' => 'SpecialAllpages', + 'Prefixindex' => 'SpecialPrefixindex', + 'Categories' => array( 'SpecialPage', 'Categories' ), 'Disambiguations' => array( 'SpecialPage', 'Disambiguations' ), + 'Listredirects' => array( 'SpecialPage', 'Listredirects' ), - 'Userlogin' => array( 'SpecialPage', 'Userlogin' ), - 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ), + # Login/create account + 'Userlogin' => array( 'SpecialPage', 'Userlogin' ), 'CreateAccount' => array( 'SpecialRedirectToSpecial', 'CreateAccount', 'Userlogin', 'signup', array( 'uselang' ) ), - 'Preferences' => array( 'SpecialPage', 'Preferences' ), - 'Watchlist' => array( 'SpecialPage', 'Watchlist' ), - 'Resetpass' => 'SpecialResetpass', + # Users and rights + 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ), + 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ), + 'Resetpass' => 'SpecialResetpass', + 'DeletedContributions' => 'DeletedContributionsPage', + 'Preferences' => array( 'SpecialPage', 'Preferences' ), + 'Contributions' => 'SpecialContributions', + 'Listgrouprights' => 'SpecialListGroupRights', + 'Listusers' => array( 'SpecialPage', 'Listusers' ), + 'Userrights' => 'UserrightsPage', + # Recent changes and logs + 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ), + 'Log' => array( 'SpecialPage', 'Log' ), + 'Watchlist' => array( 'SpecialPage', 'Watchlist' ), + 'Newpages' => 'SpecialNewpages', 'Recentchanges' => 'SpecialRecentchanges', - 'Upload' => array( 'SpecialPage', 'Upload' ), + 'Recentchangeslinked' => 'SpecialRecentchangeslinked', + 'Tags' => 'SpecialTags', + + # Media reports and uploads 'Listfiles' => array( 'SpecialPage', 'Listfiles' ), - 'Newimages' => array( 'IncludableSpecialPage', 'Newimages' ), - 'Listusers' => array( 'SpecialPage', 'Listusers' ), - 'Listgrouprights' => 'SpecialListGroupRights', - 'DeletedContributions' => 'DeletedContributionsPage', + 'Filepath' => array( 'SpecialPage', 'Filepath' ), + 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ), + 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ), + 'Upload' => array( 'SpecialPage', 'Upload' ), + + # Wiki data and tools 'Statistics' => 'SpecialStatistics', + 'Allmessages' => array( 'SpecialPage', 'Allmessages' ), + 'Version' => 'SpecialVersion', + 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ), + 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ), + + # Redirecting special pages + 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ), 'Randompage' => 'Randompage', - 'Lonelypages' => array( 'SpecialPage', 'Lonelypages' ), - 'Uncategorizedpages' => array( 'SpecialPage', 'Uncategorizedpages' ), - 'Uncategorizedcategories' => array( 'SpecialPage', 'Uncategorizedcategories' ), - 'Uncategorizedimages' => array( 'SpecialPage', 'Uncategorizedimages' ), - 'Uncategorizedtemplates' => array( 'SpecialPage', 'Uncategorizedtemplates' ), - 'Unusedcategories' => array( 'SpecialPage', 'Unusedcategories' ), - 'Unusedimages' => array( 'SpecialPage', 'Unusedimages' ), - 'Wantedpages' => array( 'IncludableSpecialPage', 'Wantedpages' ), - 'Wantedcategories' => array( 'SpecialPage', 'Wantedcategories' ), - 'Wantedfiles' => array( 'SpecialPage', 'Wantedfiles' ), - 'Wantedtemplates' => array( 'SpecialPage', 'Wantedtemplates' ), - 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ), + 'Randomredirect' => 'SpecialRandomredirect', + + # High use pages 'Mostlinkedcategories' => array( 'SpecialPage', 'Mostlinkedcategories' ), + 'Mostimages' => array( 'SpecialPage', 'Mostimages' ), + 'Mostlinked' => array( 'SpecialPage', 'Mostlinked' ), 'Mostlinkedtemplates' => array( 'SpecialPage', 'Mostlinkedtemplates' ), 'Mostcategories' => array( 'SpecialPage', 'Mostcategories' ), - 'Mostimages' => array( 'SpecialPage', 'Mostimages' ), 'Mostrevisions' => array( 'SpecialPage', 'Mostrevisions' ), - 'Fewestrevisions' => array( 'SpecialPage', 'Fewestrevisions' ), - 'Shortpages' => array( 'SpecialPage', 'Shortpages' ), - 'Longpages' => array( 'SpecialPage', 'Longpages' ), - 'Newpages' => 'SpecialNewpages', - 'Ancientpages' => array( 'SpecialPage', 'Ancientpages' ), - 'Deadendpages' => array( 'SpecialPage', 'Deadendpages' ), - 'Protectedpages' => array( 'SpecialPage', 'Protectedpages' ), - 'Protectedtitles' => array( 'SpecialPage', 'Protectedtitles' ), - 'Allpages' => 'SpecialAllpages', - 'Prefixindex' => 'SpecialPrefixindex', - 'Ipblocklist' => array( 'SpecialPage', 'Ipblocklist' ), - 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ), - 'Contributions' => 'SpecialContributions', - 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ), + + # Page tools + 'Export' => 'SpecialExport', + 'Import' => 'SpecialImport', + 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ), 'Whatlinkshere' => array( 'SpecialPage', 'Whatlinkshere' ), - 'LinkSearch' => array( 'SpecialPage', 'LinkSearch' ), - 'Recentchangeslinked' => 'SpecialRecentchangeslinked', - 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ), - 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ), + 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ), + + # Other 'Booksources' => 'SpecialBookSources', - 'Categories' => array( 'SpecialPage', 'Categories' ), - 'Export' => array( 'SpecialPage', 'Export' ), - 'Version' => 'SpecialVersion', + + # Unlisted / redirects 'Blankpage' => array( 'UnlistedSpecialPage', 'Blankpage' ), - 'Allmessages' => array( 'SpecialPage', 'Allmessages' ), - 'Log' => array( 'SpecialPage', 'Log' ), - 'Blockip' => array( 'SpecialPage', 'Blockip', 'block' ), - 'Undelete' => array( 'SpecialPage', 'Undelete', 'deletedhistory' ), - 'Import' => 'SpecialImport', - 'Lockdb' => array( 'SpecialPage', 'Lockdb', 'siteadmin' ), - 'Unlockdb' => array( 'SpecialPage', 'Unlockdb', 'siteadmin' ), - 'Userrights' => 'UserrightsPage', - 'MIMEsearch' => array( 'SpecialPage', 'MIMEsearch' ), - 'FileDuplicateSearch' => array( 'SpecialPage', 'FileDuplicateSearch' ), - 'Unwatchedpages' => array( 'SpecialPage', 'Unwatchedpages', 'unwatchedpages' ), - 'Listredirects' => array( 'SpecialPage', 'Listredirects' ), - 'Revisiondelete' => array( 'UnlistedSpecialPage', 'Revisiondelete', 'deleterevision' ), - 'Unusedtemplates' => array( 'SpecialPage', 'Unusedtemplates' ), - 'Randomredirect' => 'SpecialRandomredirect', - 'Withoutinterwiki' => array( 'SpecialPage', 'Withoutinterwiki' ), - 'Filepath' => array( 'SpecialPage', 'Filepath' ), - - 'Mypage' => array( 'SpecialMypage' ), - 'Mytalk' => array( 'SpecialMytalk' ), - 'Mycontributions' => array( 'SpecialMycontributions' ), + 'Blockme' => array( 'UnlistedSpecialPage', 'Blockme' ), + 'Emailuser' => array( 'UnlistedSpecialPage', 'Emailuser' ), 'Listadmins' => array( 'SpecialRedirectToSpecial', 'Listadmins', 'Listusers', 'sysop' ), - 'MergeHistory' => array( 'SpecialPage', 'MergeHistory', 'mergehistory' ), 'Listbots' => array( 'SpecialRedirectToSpecial', 'Listbots', 'Listusers', 'bot' ), + 'Movepage' => array( 'UnlistedSpecialPage', 'Movepage' ), + 'Mycontributions' => array( 'SpecialMycontributions' ), + 'Mypage' => array( 'SpecialMypage' ), + 'Mytalk' => array( 'SpecialMytalk' ), + 'Revisiondelete' => 'SpecialRevisionDelete', + 'Specialpages' => array( 'UnlistedSpecialPage', 'Specialpages' ), + 'Userlogout' => array( 'UnlistedSpecialPage', 'Userlogout' ), ); static public $mAliases; @@ -695,7 +715,9 @@ class SpecialPage * pages? */ public function isRestricted() { - return $this->mRestriction != ''; + global $wgGroupPermissions; + // DWIM: If all anons can do something, then it is not restricted + return $this->mRestriction != '' && empty($wgGroupPermissions['*'][$this->mRestriction]); } /** @@ -752,13 +774,25 @@ class SpecialPage } } - function outputHeader() { + /** + * Outputs a summary message on top of special pages + * Per default the message key is the canonical name of the special page + * May be overriden, i.e. by extensions to stick with the naming conventions + * for message keys: 'extensionname-xxx' + * + * @param string message key of the summary + */ + function outputHeader( $summaryMessageKey = '' ) { global $wgOut, $wgContLang; - $msg = $wgContLang->lc( $this->name() ) . '-summary'; + if( $summaryMessageKey == '' ) { + $msg = $wgContLang->lc( $this->name() ) . '-summary'; + } else { + $msg = $summaryMessageKey; + } $out = wfMsgNoTrans( $msg ); if ( ! wfEmptyMsg( $msg, $out ) and $out !== '' and ! $this->including() ) { - $wgOut->addWikiMsg( $msg ); + $wgOut->wrapWikiMsg( "<div class='mw-specialpage-summary'>\n$1</div>", $msg ); } } diff --git a/includes/SquidUpdate.php b/includes/SquidUpdate.php index c8497a83..b1f01924 100644 --- a/includes/SquidUpdate.php +++ b/includes/SquidUpdate.php @@ -52,13 +52,17 @@ class SquidUpdate { return new SquidUpdate( $blurlArr ); } - static function newFromTitles( &$titles, $urlArr = array() ) { + /** + * Create a SquidUpdate from an array of Title objects, or a TitleArray object + */ + static function newFromTitles( $titles, $urlArr = array() ) { global $wgMaxSquidPurgeTitles; - if ( count( $titles ) > $wgMaxSquidPurgeTitles ) { - $titles = array_slice( $titles, 0, $wgMaxSquidPurgeTitles ); - } + $i = 0; foreach ( $titles as $title ) { $urlArr[] = $title->getInternalURL(); + if ( $i++ > $wgMaxSquidPurgeTitles ) { + break; + } } return new SquidUpdate( $urlArr ); } diff --git a/includes/StreamFile.php b/includes/StreamFile.php index 4abd7364..bdd2a2e5 100644 --- a/includes/StreamFile.php +++ b/includes/StreamFile.php @@ -48,6 +48,7 @@ function wfStreamFile( $fname, $headers = array() ) { $modsince = preg_replace( '/;.*$/', '', $_SERVER['HTTP_IF_MODIFIED_SINCE'] ); $sinceTime = strtotime( $modsince ); if ( $stat['mtime'] <= $sinceTime ) { + ini_set('zlib.output_compression', 0); header( "HTTP/1.0 304 Not Modified" ); return; } diff --git a/includes/StubObject.php b/includes/StubObject.php index e27f0b25..f1847a39 100644 --- a/includes/StubObject.php +++ b/includes/StubObject.php @@ -154,7 +154,7 @@ class StubUserLang extends StubObject { } # Validate $code - if( empty( $code ) || !preg_match( '/^[a-z-]+$/', $code ) ) { + if( empty( $code ) || !preg_match( '/^[a-z-]+$/', $code ) || ( $code === 'qqq' ) ) { wfDebug( "Invalid user language code\n" ); $code = $wgContLanguageCode; } diff --git a/includes/Title.php b/includes/Title.php index 515a3b65..f6c0d5de 100644 --- a/includes/Title.php +++ b/includes/Title.php @@ -69,6 +69,7 @@ class Title { var $mLength = -1; ///< The page length, 0 for special pages var $mRedirect = null; ///< Is the article at this title a redirect? var $mNotificationTimestamp = array(); ///< Associative array of user ID -> timestamp/false + var $mBacklinkCache = null; ///< Cache of links to this title //@} @@ -294,11 +295,77 @@ class Title { /** * Extract a redirect destination from a string and return the * Title, or null if the text doesn't contain a valid redirect + * This will only return the very next target, useful for + * the redirect table and other checks that don't need full recursion * - * @param $text \type{String} Text with possible redirect + * @param $text \type{\string} Text with possible redirect * @return \type{Title} The corresponding Title */ public static function newFromRedirect( $text ) { + return self::newFromRedirectInternal( $text ); + } + + /** + * Extract a redirect destination from a string and return the + * Title, or null if the text doesn't contain a valid redirect + * This will recurse down $wgMaxRedirects times or until a non-redirect target is hit + * in order to provide (hopefully) the Title of the final destination instead of another redirect + * + * @param $text \type{\string} Text with possible redirect + * @return \type{Title} The corresponding Title + */ + public static function newFromRedirectRecurse( $text ) { + $titles = self::newFromRedirectArray( $text ); + return $titles ? array_pop( $titles ) : null; + } + + /** + * Extract a redirect destination from a string and return an + * array of Titles, or null if the text doesn't contain a valid redirect + * The last element in the array is the final destination after all redirects + * have been resolved (up to $wgMaxRedirects times) + * + * @param $text \type{\string} Text with possible redirect + * @return \type{\array} Array of Titles, with the destination last + */ + public static function newFromRedirectArray( $text ) { + global $wgMaxRedirects; + // are redirects disabled? + if( $wgMaxRedirects < 1 ) + return null; + $title = self::newFromRedirectInternal( $text ); + if( is_null( $title ) ) + return null; + // recursive check to follow double redirects + $recurse = $wgMaxRedirects; + $titles = array( $title ); + while( --$recurse > 0 ) { + if( $title->isRedirect() ) { + $article = new Article( $title, 0 ); + $newtitle = $article->getRedirectTarget(); + } else { + break; + } + // Redirects to some special pages are not permitted + if( $newtitle instanceOf Title && $newtitle->isValidRedirectTarget() ) { + // the new title passes the checks, so make that our current title so that further recursion can be checked + $title = $newtitle; + $titles[] = $newtitle; + } else { + break; + } + } + return $titles; + } + + /** + * Really extract the redirect destination + * Do not call this function directly, use one of the newFromRedirect* functions above + * + * @param $text \type{\string} Text with possible redirect + * @return \type{Title} The corresponding Title + */ + protected static function newFromRedirectInternal( $text ) { $redir = MagicWord::get( 'redirect' ); $text = trim($text); if( $redir->matchStartAndRemove( $text ) ) { @@ -316,13 +383,11 @@ class Title { $m[1] = urldecode( ltrim( $m[1], ':' ) ); } $title = Title::newFromText( $m[1] ); - // Redirects to some special pages are not permitted - if( $title instanceof Title - && !$title->isSpecial( 'Userlogout' ) - && !$title->isSpecial( 'Filepath' ) ) - { - return $title; + // If the title is a redirect to bad special pages or is invalid, return null + if( !$title instanceof Title || !$title->isValidRedirectTarget() ) { + return null; } + return $title; } } return null; @@ -802,19 +867,21 @@ class Title { * @return \type{\string} the URL */ public function getLinkUrl( $query = array(), $variant = false ) { + wfProfileIn( __METHOD__ ); if( !is_array( $query ) ) { + wfProfileOut( __METHOD__ ); throw new MWException( 'Title::getLinkUrl passed a non-array for '. '$query' ); } if( $this->isExternal() ) { - return $this->getFullURL( $query ); - } elseif( $this->getPrefixedText() === '' - and $this->getFragment() !== '' ) { - return $this->getFragmentForURL(); + $ret = $this->getFullURL( $query ); + } elseif( $this->getPrefixedText() === '' && $this->getFragment() !== '' ) { + $ret = $this->getFragmentForURL(); } else { - return $this->getLocalURL( $query, $variant ) - . $this->getFragmentForURL(); + $ret = $this->getLocalURL( $query, $variant ) . $this->getFragmentForURL(); } + wfProfileOut( __METHOD__ ); + return $ret; } /** @@ -992,7 +1059,7 @@ class Title { */ public function userCan( $action, $doExpensiveQueries = true ) { global $wgUser; - return ( $this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries ) === array()); + return ($this->getUserPermissionsErrorsInternal( $action, $wgUser, $doExpensiveQueries, true ) === array()); } /** @@ -1024,7 +1091,7 @@ class Title { } // Edit blocks should not affect reading. Account creation blocks handled at userlogin. - if ( $user->isBlockedFrom( $this ) && $action != 'read' && $action != 'createaccount' ) { + if ( $action != 'read' && $action != 'createaccount' && $user->isBlockedFrom( $this ) ) { $block = $user->mBlock; // This is from OutputPage::blockedPage @@ -1094,19 +1161,73 @@ class Title { * @param $action \type{\string} action that permission needs to be checked for * @param $user \type{User} user to check * @param $doExpensiveQueries \type{\bool} Set this to false to avoid doing unnecessary queries. + * @param $short \type{\bool} Set this to true to stop after the first permission error. * @return \type{\array} Array of arrays of the arguments to wfMsg to explain permissions problems. */ - private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries = true ) { + private function getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries=true, $short=false ) { wfProfileIn( __METHOD__ ); $errors = array(); + // First stop is permissions checks, which fail most often, and which are easiest to test. + if ( $action == 'move' ) { + if( !$user->isAllowed( 'move-rootuserpages' ) + && $this->getNamespace() == NS_USER && !$this->isSubpage() ) + { + // Show user page-specific message only if the user can move other pages + $errors[] = array( 'cant-move-user-page' ); + } + + // Check if user is allowed to move files if it's a file + if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) { + $errors[] = array( 'movenotallowedfile' ); + } + + if( !$user->isAllowed( 'move' ) ) { + // User can't move anything + $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed'); + } + } elseif ( $action == 'create' ) { + if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) || + ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) + { + $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin'); + } + } elseif( $action == 'move-target' ) { + if( !$user->isAllowed( 'move' ) ) { + // User can't move anything + $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed'); + } elseif( !$user->isAllowed( 'move-rootuserpages' ) + && $this->getNamespace() == NS_USER && !$this->isSubpage() ) + { + // Show user page-specific message only if the user can move other pages + $errors[] = array( 'cant-move-to-user-page' ); + } + } elseif( !$user->isAllowed( $action ) ) { + $return = null; + $groups = array_map( array( 'User', 'makeGroupLinkWiki' ), + User::getGroupsWithPermission( $action ) ); + if( $groups ) { + $return = array( 'badaccess-groups', + array( implode( ', ', $groups ), count( $groups ) ) ); + } else { + $return = array( "badaccess-group0" ); + } + $errors[] = $return; + } + + # Short-circuit point + if( $short && count($errors) > 0 ) { + wfProfileOut( __METHOD__ ); + return $errors; + } + // Use getUserPermissionsErrors instead if( !wfRunHooks( 'userCan', array( &$this, &$user, $action, &$result ) ) ) { wfProfileOut( __METHOD__ ); return $result ? array() : array( array( 'badaccess-group0' ) ); } - + // Check getUserPermissionsErrors hook if( !wfRunHooks( 'getUserPermissionsErrors', array(&$this,&$user,$action,&$result) ) ) { if( is_array($result) && count($result) && !is_array($result[0]) ) $errors[] = $result; # A single array representing an error @@ -1117,6 +1238,12 @@ class Title { else if( $result === false ) $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that" } + # Short-circuit point + if( $short && count($errors) > 0 ) { + wfProfileOut( __METHOD__ ); + return $errors; + } + // Check getUserPermissionsErrorsExpensive hook if( $doExpensiveQueries && !wfRunHooks( 'getUserPermissionsErrorsExpensive', array(&$this,&$user,$action,&$result) ) ) { if( is_array($result) && count($result) && !is_array($result[0]) ) $errors[] = $result; # A single array representing an error @@ -1127,13 +1254,20 @@ class Title { else if( $result === false ) $errors[] = array('badaccess-group0'); # a generic "We don't want them to do that" } + # Short-circuit point + if( $short && count($errors) > 0 ) { + wfProfileOut( __METHOD__ ); + return $errors; + } - // TODO: document + # Only 'createaccount' and 'execute' can be performed on + # special pages, which don't actually exist in the DB. $specialOKActions = array( 'createaccount', 'execute' ); if( NS_SPECIAL == $this->mNamespace && !in_array( $action, $specialOKActions) ) { $errors[] = array('ns-specialprotected'); } + # Check $wgNamespaceProtection for restricted namespaces if( $this->isNamespaceProtected() ) { $ns = $this->getNamespace() == NS_MAIN ? wfMsg( 'nstab-main' ) : $this->getNsText(); @@ -1141,7 +1275,7 @@ class Title { array('protectedinterface') : array( 'namespaceprotected', $ns ); } - # protect css/js subpages of user pages + # Protect css/js subpages of user pages # XXX: this might be better using restrictions # XXX: Find a way to work around the php bug that prevents using $this->userCanEditCssJsSubpage() from working if( $this->isCssJsSubpage() && !$user->isAllowed('editusercssjs') @@ -1150,6 +1284,32 @@ class Title { $errors[] = array('customcssjsprotected'); } + # Check against page_restrictions table requirements on this + # page. The user must possess all required rights for this action. + foreach( $this->getRestrictions($action) as $right ) { + // Backwards compatibility, rewrite sysop -> protect + if( $right == 'sysop' ) { + $right = 'protect'; + } + if( '' != $right && !$user->isAllowed( $right ) ) { + // Users with 'editprotected' permission can edit protected pages + if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) { + // Users with 'editprotected' permission cannot edit protected pages + // with cascading option turned on. + if( $this->mCascadeRestriction ) { + $errors[] = array( 'protectedpagetext', $right ); + } + } else { + $errors[] = array( 'protectedpagetext', $right ); + } + } + } + # Short-circuit point + if( $short && count($errors) > 0 ) { + wfProfileOut( __METHOD__ ); + return $errors; + } + if( $doExpensiveQueries && !$this->isCssJsSubpage() ) { # We /could/ use the protection level on the source page, but it's fairly ugly # as we have to establish a precedence hierarchy for pages included by multiple @@ -1172,26 +1332,10 @@ class Title { } } } - - foreach( $this->getRestrictions($action) as $right ) { - // Backwards compatibility, rewrite sysop -> protect - if( $right == 'sysop' ) { - $right = 'protect'; - } - if( '' != $right && !$user->isAllowed( $right ) ) { - // Users with 'editprotected' permission can edit protected pages - if( $action=='edit' && $user->isAllowed( 'editprotected' ) ) { - // Users with 'editprotected' permission cannot edit protected pages - // with cascading option turned on. - if( $this->mCascadeRestriction ) { - $errors[] = array( 'protectedpagetext', $right ); - } else { - // Nothing, user can edit! - } - } else { - $errors[] = array( 'protectedpagetext', $right ); - } - } + # Short-circuit point + if( $short && count($errors) > 0 ) { + wfProfileOut( __METHOD__ ); + return $errors; } if( $action == 'protect' ) { @@ -1212,26 +1356,7 @@ class Title { $errors[] = array( 'titleprotected', User::whoIs($pt_user), $pt_reason ); } } - - if( ( $this->isTalkPage() && !$user->isAllowed( 'createtalk' ) ) || - ( !$this->isTalkPage() && !$user->isAllowed( 'createpage' ) ) ) - { - $errors[] = $user->isAnon() ? array ('nocreatetext') : array ('nocreate-loggedin'); - } } elseif( $action == 'move' ) { - if( !$user->isAllowed( 'move' ) ) { - // User can't move anything - $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed'); - } elseif( !$user->isAllowed( 'move-rootuserpages' ) - && $this->getNamespace() == NS_USER && !$this->isSubpage() ) - { - // Show user page-specific message only if the user can move other pages - $errors[] = array( 'cant-move-user-page' ); - } - // Check if user is allowed to move files if it's a file - if( $this->getNamespace() == NS_FILE && !$user->isAllowed( 'movefile' ) ) { - $errors[] = array( 'movenotallowedfile' ); - } // Check for immobile pages if( !MWNamespace::isMovable( $this->getNamespace() ) ) { // Specific message for this case @@ -1241,31 +1366,11 @@ class Title { $errors[] = array( 'immobile-page' ); } } elseif( $action == 'move-target' ) { - if( !$user->isAllowed( 'move' ) ) { - // User can't move anything - $errors[] = $user->isAnon() ? array ( 'movenologintext' ) : array ('movenotallowed'); - } elseif( !$user->isAllowed( 'move-rootuserpages' ) - && $this->getNamespace() == NS_USER && !$this->isSubpage() ) - { - // Show user page-specific message only if the user can move other pages - $errors[] = array( 'cant-move-to-user-page' ); - } if( !MWNamespace::isMovable( $this->getNamespace() ) ) { $errors[] = array( 'immobile-target-namespace', $this->getNsText() ); } elseif( !$this->isMovable() ) { $errors[] = array( 'immobile-target-page' ); } - } elseif( !$user->isAllowed( $action ) ) { - $return = null; - $groups = array_map( array( 'User', 'makeGroupLinkWiki' ), - User::getGroupsWithPermission( $action ) ); - if( $groups ) { - $return = array( 'badaccess-groups', - array( implode( ', ', $groups ), count( $groups ) ) ); - } else { - $return = array( "badaccess-group0" ); - } - $errors[] = $return; } wfProfileOut( __METHOD__ ); @@ -1412,7 +1517,7 @@ class Title { } # Shortcut for public wikis, allows skipping quite a bit of code - if ($wgGroupPermissions['*']['read']) + if ( !empty( $wgGroupPermissions['*']['read'] ) ) return true; if( $wgUser->isAllowed( 'read' ) ) { @@ -1510,11 +1615,36 @@ class Title { return $this->mHasSubpages; } - $db = wfGetDB( DB_SLAVE ); - return $this->mHasSubpages = (bool)$db->selectField( 'page', '1', - "page_namespace = {$this->mNamespace} AND page_title LIKE '" - . $db->escapeLike( $this->mDbkeyform ) . "/%'", - __METHOD__ + $subpages = $this->getSubpages( 1 ); + if( $subpages instanceof TitleArray ) + return $this->mHasSubpages = (bool)$subpages->count(); + return $this->mHasSubpages = false; + } + + /** + * Get all subpages of this page. + * @param $limit Maximum number of subpages to fetch; -1 for no limit + * @return mixed TitleArray, or empty array if this page's namespace + * doesn't allow subpages + */ + public function getSubpages( $limit = -1 ) { + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) + return array(); + + $dbr = wfGetDB( DB_SLAVE ); + $conds['page_namespace'] = $this->getNamespace(); + $conds[] = 'page_title LIKE ' . $dbr->addQuotes( + $dbr->escapeLike( $this->getDBkey() ) . '/%' ); + $options = array(); + if( $limit > -1 ) + $options['LIMIT'] = $limit; + return $this->mSubpages = TitleArray::newFromResult( + $dbr->select( 'page', + array( 'page_id', 'page_namespace', 'page_title', 'page_is_redirect' ), + $conds, + __METHOD__, + $options + ) ); } @@ -1849,20 +1979,45 @@ class Title { * @return \type{\int} the number of archived revisions */ public function isDeleted() { - $fname = 'Title::isDeleted'; - if ( $this->getNamespace() < 0 ) { + if( $this->getNamespace() < 0 ) { $n = 0; } else { $dbr = wfGetDB( DB_SLAVE ); - $n = $dbr->selectField( 'archive', 'COUNT(*)', array( 'ar_namespace' => $this->getNamespace(), - 'ar_title' => $this->getDBkey() ), $fname ); + $n = $dbr->selectField( 'archive', 'COUNT(*)', + array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), + __METHOD__ + ); if( $this->getNamespace() == NS_FILE ) { $n += $dbr->selectField( 'filearchive', 'COUNT(*)', - array( 'fa_name' => $this->getDBkey() ), $fname ); + array( 'fa_name' => $this->getDBkey() ), + __METHOD__ + ); } } return (int)$n; } + + /** + * Is there a version of this page in the deletion archive? + * @return bool + */ + public function isDeletedQuick() { + if( $this->getNamespace() < 0 ) { + return false; + } + $dbr = wfGetDB( DB_SLAVE ); + $deleted = (bool)$dbr->selectField( 'archive', '1', + array( 'ar_namespace' => $this->getNamespace(), 'ar_title' => $this->getDBkey() ), + __METHOD__ + ); + if( !$deleted && $this->getNamespace() == NS_FILE ) { + $deleted = (bool)$dbr->selectField( 'filearchive', '1', + array( 'fa_name' => $this->getDBkey() ), + __METHOD__ + ); + } + return $deleted; + } /** * Get the article ID for this Title from the link cache, @@ -1955,7 +2110,7 @@ class Title { $linkCache = LinkCache::singleton(); $linkCache->clearBadLink( $this->getPrefixedDBkey() ); - if ( 0 == $newid ) { $this->mArticleID = -1; } + if ( $newid === false ) { $this->mArticleID = -1; } else { $this->mArticleID = $newid; } $this->mRestrictionsLoaded = false; $this->mRestrictions = array(); @@ -2064,14 +2219,22 @@ class Title { # Namespace or interwiki prefix $firstPass = true; + $prefixRegexp = "/^(.+?)_*:_*(.*)$/S"; do { $m = array(); - if ( preg_match( "/^(.+?)_*:_*(.*)$/S", $dbkey, $m ) ) { + if ( preg_match( $prefixRegexp, $dbkey, $m ) ) { $p = $m[1]; - if ( $ns = $wgContLang->getNsIndex( $p )) { + if ( $ns = $wgContLang->getNsIndex( $p ) ) { # Ordinary namespace $dbkey = $m[2]; $this->mNamespace = $ns; + # For Talk:X pages, check if X has a "namespace" prefix + if( $ns == NS_TALK && preg_match( $prefixRegexp, $dbkey, $x ) ) { + if( $wgContLang->getNsIndex( $x[1] ) ) + return false; # Disallow Talk:File:x type titles... + else if( Interwiki::isValidInterwiki( $x[1] ) ) + return false; # Disallow Talk:Interwiki:x type titles... + } } elseif( Interwiki::isValidInterwiki( $p ) ) { if( !$firstPass ) { # Can't make a local interwiki link to an interwiki link. @@ -2254,13 +2417,13 @@ class Title { * WARNING: do not use this function on arbitrary user-supplied titles! * On heavily-used templates it will max out the memory. * - * @param $options \type{\string} may be FOR UPDATE + * @param array $options may be FOR UPDATE * @return \type{\arrayof{Title}} the Title objects linking here */ - public function getLinksTo( $options = '', $table = 'pagelinks', $prefix = 'pl' ) { + public function getLinksTo( $options = array(), $table = 'pagelinks', $prefix = 'pl' ) { $linkCache = LinkCache::singleton(); - if ( $options ) { + if ( count( $options ) > 0 ) { $db = wfGetDB( DB_MASTER ); } else { $db = wfGetDB( DB_SLAVE ); @@ -2295,10 +2458,10 @@ class Title { * WARNING: do not use this function on arbitrary user-supplied titles! * On heavily-used templates it will max out the memory. * - * @param $options \type{\string} may be FOR UPDATE + * @param array $options may be FOR UPDATE * @return \type{\arrayof{Title}} the Title objects linking here */ - public function getTemplateLinksTo( $options = '' ) { + public function getTemplateLinksTo( $options = array() ) { return $this->getLinksTo( $options, 'templatelinks', 'tl' ); } @@ -2306,42 +2469,35 @@ class Title { * Get an array of Title objects referring to non-existent articles linked from this page * * @todo check if needed (used only in SpecialBrokenRedirects.php, and should use redirect table in this case) - * @param $options \type{\string} may be FOR UPDATE * @return \type{\arrayof{Title}} the Title objects */ - public function getBrokenLinksFrom( $options = '' ) { + public function getBrokenLinksFrom() { if ( $this->getArticleId() == 0 ) { # All links from article ID 0 are false positives return array(); } - if ( $options ) { - $db = wfGetDB( DB_MASTER ); - } else { - $db = wfGetDB( DB_SLAVE ); - } - - $res = $db->safeQuery( - "SELECT pl_namespace, pl_title - FROM ! - LEFT JOIN ! - ON pl_namespace=page_namespace - AND pl_title=page_title - WHERE pl_from=? - AND page_namespace IS NULL - !", - $db->tableName( 'pagelinks' ), - $db->tableName( 'page' ), - $this->getArticleId(), - $options ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( + array( 'page', 'pagelinks' ), + array( 'pl_namespace', 'pl_title' ), + array( + 'pl_from' => $this->getArticleId(), + 'page_namespace IS NULL' + ), + __METHOD__, array(), + array( + 'page' => array( + 'LEFT JOIN', + array( 'pl_namespace=page_namespace', 'pl_title=page_title' ) + ) + ) + ); $retVal = array(); - if ( $db->numRows( $res ) ) { - foreach( $res as $row ) { - $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title ); - } + foreach( $res as $row ) { + $retVal[] = Title::makeTitle( $row->pl_namespace, $row->pl_title ); } - $db->freeResult( $res ); return $retVal; } @@ -2459,7 +2615,7 @@ class Title { $nt->getUserPermissionsErrors('edit', $wgUser) ); } - $match = EditPage::matchSpamRegex( $reason ); + $match = EditPage::matchSummarySpamRegex( $reason ); if( $match !== false ) { // This is kind of lame, won't display nice $errors[] = array('spamprotectiontext'); @@ -2559,8 +2715,8 @@ class Title { ); # Update the protection log $log = new LogPage( 'protect' ); - $comment = wfMsgForContent('prot_1movedto2',$this->getPrefixedText(), $nt->getPrefixedText() ); - if( $reason ) $comment .= ': ' . $reason; + $comment = wfMsgForContent( 'prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText() ); + if( $reason ) $comment .= wfMsgForContent( 'colon-separator' ) . $reason; $log->addEntry( 'move_prot', $nt, $comment, array($this->getPrefixedText()) ); // FIXME: $params? } @@ -2601,8 +2757,16 @@ class Title { # Update message cache for interface messages if( $nt->getNamespace() == NS_MEDIAWIKI ) { global $wgMessageCache; - $oldarticle = new Article( $this ); - $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() ); + + # @bug 17860: old article can be deleted, if this the case, + # delete it from message cache + if ( $this->getArticleID === 0 ) { + $wgMessageCache->replace( $this->getDBkey(), false ); + } else { + $oldarticle = new Article( $this ); + $wgMessageCache->replace( $this->getDBkey(), $oldarticle->getContent() ); + } + $newarticle = new Article( $nt ); $wgMessageCache->replace( $nt->getDBkey(), $newarticle->getContent() ); } @@ -2830,6 +2994,67 @@ class Title { } /** + * Move this page's subpages to be subpages of $nt + * @param $nt Title Move target + * @param $auth bool Whether $wgUser's permissions should be checked + * @param $reason string The reason for the move + * @param $createRedirect bool Whether to create redirects from the old subpages to the new ones + * Ignored if the user doesn't have the 'suppressredirect' right + * @return mixed array with old page titles as keys, and strings (new page titles) or + * arrays (errors) as values, or an error array with numeric indices if no pages were moved + */ + public function moveSubpages( $nt, $auth = true, $reason = '', $createRedirect = true ) { + global $wgUser, $wgMaximumMovedPages; + // Check permissions + if( !$this->userCan( 'move-subpages' ) ) + return array( 'cant-move-subpages' ); + // Do the source and target namespaces support subpages? + if( !MWNamespace::hasSubpages( $this->getNamespace() ) ) + return array( 'namespace-nosubpages', + MWNamespace::getCanonicalName( $this->getNamespace() ) ); + if( !MWNamespace::hasSubpages( $nt->getNamespace() ) ) + return array( 'namespace-nosubpages', + MWNamespace::getCanonicalName( $nt->getNamespace() ) ); + + $subpages = $this->getSubpages($wgMaximumMovedPages + 1); + $retval = array(); + $count = 0; + foreach( $subpages as $oldSubpage ) { + $count++; + if( $count > $wgMaximumMovedPages ) { + $retval[$oldSubpage->getPrefixedTitle()] = + array( 'movepage-max-pages', + $wgMaximumMovedPages ); + break; + } + + if( $oldSubpage->getArticleId() == $this->getArticleId() ) + // When moving a page to a subpage of itself, + // don't move it twice + continue; + $newPageName = preg_replace( + '#^'.preg_quote( $this->getDBKey(), '#' ).'#', + $nt->getDBKey(), $oldSubpage->getDBKey() ); + if( $oldSubpage->isTalkPage() ) { + $newNs = $nt->getTalkPage()->getNamespace(); + } else { + $newNs = $nt->getSubjectPage()->getNamespace(); + } + # Bug 14385: we need makeTitleSafe because the new page names may + # be longer than 255 characters. + $newSubpage = Title::makeTitleSafe( $newNs, $newPageName ); + + $success = $oldSubpage->moveTo( $newSubpage, $auth, $reason, $createRedirect ); + if( $success === true ) { + $retval[$oldSubpage->getPrefixedText()] = $newSubpage->getPrefixedText(); + } else { + $retval[$oldSubpage->getPrefixedText()] = $success; + } + } + return $retval; + } + + /** * Checks if this page is just a one-rev redirect. * Adds lock, so don't use just for light purposes. * @@ -2842,7 +3067,7 @@ class Title { array( 'page_is_redirect', 'page_latest', 'page_id' ), $this->pageCond(), __METHOD__, - 'FOR UPDATE' + array( 'FOR UPDATE' ) ); # Cache some fields we may want $this->mArticleID = $row ? intval($row->page_id) : 0; @@ -2860,7 +3085,7 @@ class Title { 'page_latest != rev_id' ), __METHOD__, - 'FOR UPDATE' + array( 'FOR UPDATE' ) ); # Return true if there was no history return ($row === false); @@ -3034,6 +3259,28 @@ class Title { } /** + * Get the first revision of the page + * + * @param $flags \type{\int} GAID_FOR_UPDATE + * @return Revision (or NULL if page doesn't exist) + */ + public function getFirstRevision( $flags=0 ) { + $db = ($flags & GAID_FOR_UPDATE) ? wfGetDB( DB_MASTER ) : wfGetDB( DB_SLAVE ); + $pageId = $this->getArticleId($flags); + if( !$pageId ) return NULL; + $row = $db->selectRow( 'revision', '*', + array( 'rev_page' => $pageId ), + __METHOD__, + array( 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => 1 ) + ); + if( !$row ) { + return NULL; + } else { + return new Revision( $row ); + } + } + + /** * Check if this is a new page * * @return bool @@ -3074,8 +3321,8 @@ class Title { 'rev_page = ' . intval( $this->getArticleId() ) . ' AND rev_id > ' . intval( $old ) . ' AND rev_id < ' . intval( $new ), - __METHOD__, - array( 'USE INDEX' => 'PRIMARY' ) ); + __METHOD__ + ); } /** @@ -3094,7 +3341,7 @@ class Title { /** * Callback for usort() to do title sorts by (namespace, title) */ - static function compare( $a, $b ) { + public static function compare( $a, $b ) { if( $a->getNamespace() == $b->getNamespace() ) { return strcmp( $a->getText(), $b->getText() ); } else { @@ -3144,7 +3391,7 @@ class Title { if( $this->mInterwiki != '' ) { return true; // any interwiki link might be viewable, for all we know } - switch( $this->mNamespace ) { + switch( $this->mNamespace ) { case NS_MEDIA: case NS_FILE: return wfFindFile( $this ); // file exists, possibly in a foreign repo @@ -3250,9 +3497,9 @@ class Title { * @return \type{\string} Trackback URL */ public function trackbackURL() { - global $wgScriptPath, $wgServer; + global $wgScriptPath, $wgServer, $wgScriptExtension; - return "$wgServer$wgScriptPath/trackback.php?article=" + return "$wgServer$wgScriptPath/trackback$wgScriptExtension?article=" . htmlspecialchars(urlencode($this->getPrefixedDBkey())); } @@ -3396,4 +3643,36 @@ class Title { } return $redirs; } + + /** + * Check if this Title is a valid redirect target + * + * @return \type{\bool} TRUE or FALSE + */ + public function isValidRedirectTarget() { + global $wgInvalidRedirectTargets; + + // invalid redirect targets are stored in a global array, but explicity disallow Userlogout here + if( $this->isSpecial( 'Userlogout' ) ) { + return false; + } + + foreach( $wgInvalidRedirectTargets as $target ) { + if( $this->isSpecial( $target ) ) { + return false; + } + } + + return true; + } + + /** + * Get a backlink cache object + */ + function getBacklinkCache() { + if ( is_null( $this->mBacklinkCache ) ) { + $this->mBacklinkCache = new BacklinkCache( $this ); + } + return $this->mBacklinkCache; + } } diff --git a/includes/UploadBase.php b/includes/UploadBase.php deleted file mode 100644 index 91155a1b..00000000 --- a/includes/UploadBase.php +++ /dev/null @@ -1,867 +0,0 @@ -<?php - -class UploadBase { - var $mTempPath; - var $mDesiredDestName, $mDestName, $mRemoveTempFile, $mSourceType; - var $mTitle = false, $mTitleError = 0; - var $mFilteredName, $mFinalExtension; - - const SUCCESS = 0; - const OK = 0; - const BEFORE_PROCESSING = 1; - const LARGE_FILE_SERVER = 2; - const EMPTY_FILE = 3; - const MIN_LENGTH_PARTNAME = 4; - const ILLEGAL_FILENAME = 5; - const PROTECTED_PAGE = 6; - const OVERWRITE_EXISTING_FILE = 7; - const FILETYPE_MISSING = 8; - const FILETYPE_BADTYPE = 9; - const VERIFICATION_ERROR = 10; - const UPLOAD_VERIFICATION_ERROR = 11; - const UPLOAD_WARNING = 12; - const INTERNAL_ERROR = 13; - - const SESSION_VERSION = 2; - - /** - * Returns true if uploads are enabled. - * Can be overriden by subclasses. - */ - static function isEnabled() { - global $wgEnableUploads; - return $wgEnableUploads; - } - /** - * Returns true if the user can use this upload module or else a string - * identifying the missing permission. - * Can be overriden by subclasses. - */ - static function isAllowed( $user ) { - if( !$user->isAllowed( 'upload' ) ) - return 'upload'; - return true; - } - - // Upload handlers. Should probably just be a global - static $uploadHandlers = array( 'Stash', 'Upload', 'Url' ); - /** - * Create a form of UploadBase depending on wpSourceType and initializes it - */ - static function createFromRequest( &$request, $type = null ) { - $type = $type ? $type : $request->getVal( 'wpSourceType' ); - if( !$type ) - return null; - $type = ucfirst($type); - $className = 'UploadFrom'.$type; - if( !in_array( $type, self::$uploadHandlers ) ) - return null; - if( !call_user_func( array( $className, 'isEnabled' ) ) ) - return null; - if( !call_user_func( array( $className, 'isValidRequest' ), $request ) ) - return null; - - $handler = new $className; - $handler->initializeFromRequest( $request ); - return $handler; - } - - /** - * Check whether a request if valid for this handler - */ - static function isValidRequest( $request ) { - return false; - } - - function __construct() {} - - /** - * Do the real variable initialization - */ - function initialize( $name, $tempPath, $fileSize, $removeTempFile = false ) { - $this->mDesiredDestName = $name; - $this->mTempPath = $tempPath; - $this->mFileSize = $fileSize; - $this->mRemoveTempFile = $removeTempFile; - } - - /** - * Fetch the file. Usually a no-op - */ - function fetchFile() { - return self::OK; - } - - /** - * Verify whether the upload is sane. - * Returns self::OK or else an array with error information - */ - function verifyUpload() { - global $wgUser; - - /** - * If there was no filename or a zero size given, give up quick. - */ - if( empty( $this->mFileSize ) ) - return array( 'status' => self::EMPTY_FILE ); - - $nt = $this->getTitle(); - if( is_null( $nt ) ) { - $result = array( 'status' => $this->mTitleError ); - if( $this->mTitleError == self::ILLEGAL_FILENAME ) - $resul['filtered'] = $this->mFilteredName; - if ( $this->mTitleError == self::FILETYPE_BADTYPE ) - $result['finalExt'] = $this->mFinalExtension; - return $result; - } - $this->mLocalFile = wfLocalFile( $nt ); - $this->mDestName = $this->mLocalFile->getName(); - - /** - * In some cases we may forbid overwriting of existing files. - */ - $overwrite = $this->checkOverwrite( $this->mDestName ); - if( $overwrite !== true ) - return array( 'status' => self::OVERWRITE_EXISTING_FILE, 'overwrite' => $overwrite ); - - /** - * Look at the contents of the file; if we can recognize the - * type but it's corrupt or data of the wrong type, we should - * probably not accept it. - */ - $verification = $this->verifyFile( $this->mTempPath ); - - if( $verification !== true ) { - if( !is_array( $verification ) ) - $verification = array( $verification ); - $verification['status'] = self::VERIFICATION_ERROR; - return $verification; - } - - $error = ''; - if( !wfRunHooks( 'UploadVerification', - array( $this->mDestName, $this->mTempPath, &$error ) ) ) { - return array( 'status' => self::UPLOAD_VERIFICATION_ERROR, 'error' => $error ); - } - - return self::OK; - } - - /** - * Verifies that it's ok to include the uploaded file - * - * @param string $tmpfile the full path of the temporary file to verify - * @return mixed true of the file is verified, a string or array otherwise. - */ - protected function verifyFile( $tmpfile ) { - $this->mFileProps = File::getPropsFromPath( $this->mTempPath, - $this->mFinalExtension ); - $this->checkMacBinary(); - - #magically determine mime type - $magic = MimeMagic::singleton(); - $mime = $magic->guessMimeType( $tmpfile, false ); - - #check mime type, if desired - global $wgVerifyMimeType; - if ( $wgVerifyMimeType ) { - - wfDebug ( "\n\nmime: <$mime> extension: <{$this->mFinalExtension}>\n\n"); - #check mime type against file extension - if( !self::verifyExtension( $mime, $this->mFinalExtension ) ) { - return 'uploadcorrupt'; - } - - #check mime type blacklist - global $wgMimeTypeBlacklist; - if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) - && $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) { - return array( 'filetype-badmime', $mime ); - } - } - - #check for htmlish code and javascript - if( $this->detectScript ( $tmpfile, $mime, $this->mFinalExtension ) ) { - return 'uploadscripted'; - } - - /** - * Scan the uploaded file for viruses - */ - $virus = $this->detectVirus($tmpfile); - if ( $virus ) { - return array( 'uploadvirus', $virus ); - } - - wfDebug( __METHOD__.": all clear; passing.\n" ); - return true; - } - - /** - * Check whether the user can edit, upload and create the image - */ - function verifyPermissions( $user ) { - /** - * If the image is protected, non-sysop users won't be able - * to modify it by uploading a new revision. - */ - $nt = $this->getTitle(); - if( is_null( $nt ) ) - return true; - $permErrors = $nt->getUserPermissionsErrors( 'edit', $user ); - $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $user ); - $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $user ) ); - if( $permErrors || $permErrorsUpload || $permErrorsCreate ) { - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) ); - $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) ); - return $permErrors; - } - return true; - } - - /** - * Check for non fatal problems with the file - */ - function checkWarnings() { - $warning = array(); - - $filename = $this->mLocalFile->getName(); - $n = strrpos( $filename, '.' ); - $partname = $n ? substr( $filename, 0, $n ) : $filename; - - // Check whether the resulting filename is different from the desired one - if( $this->mDesiredDestName != $filename ) - $warning['badfilename'] = $filename; - - // Check whether the file extension is on the unwanted list - global $wgCheckFileExtensions, $wgFileExtensions; - if ( $wgCheckFileExtensions ) { - if ( !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) - $warning['filetype-unwanted-type'] = $this->mFinalExtension; - } - - global $wgUploadSizeWarning; - if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) - $warning['large-file'] = $wgUploadSizeWarning; - - if ( $this->mFileSize == 0 ) - $warning['emptyfile'] = true; - - $exists = self::getExistsWarning( $this->mLocalFile ); - if( $exists !== false ) - $warning['exists'] = $exists; - - // Check whether this may be a thumbnail - if( $exists !== false && $exists[0] != 'thumb' - && self::isThumbName( $this->mLocalFile->getName() ) ) - $warning['file-thumbnail-no'] = substr( $filename , 0, - strpos( $nt->getText() , '-' ) +1 ); - - $hash = File::sha1Base36( $this->mTempPath ); - $dupes = RepoGroup::singleton()->findBySha1( $hash ); - if( $dupes ) - $warning['duplicate'] = $dupes; - - $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist(); - foreach( $filenamePrefixBlacklist as $prefix ) { - if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) { - $warning['filename-bad-prefix'] = $prefix; - break; - } - } - - # If the file existed before and was deleted, warn the user of this - # Don't bother doing so if the file exists now, however - if( $this->mLocalFile->wasDeleted() && !$this->mLocalFile->exists() ) - $warning['filewasdeleted'] = $this->mLocalFile->getTitle(); - - return $warning; - } - - /** - * Really perform the upload. - */ - function performUpload( $comment, $pageText, $watch, $user ) { - $status = $this->mLocalFile->upload( $this->mTempPath, $comment, $pageText, - File::DELETE_SOURCE, $this->mFileProps, false, $user ); - - if( $status->isGood() && $watch ) { - $user->addWatch( $this->mLocalFile->getTitle() ); - } - - if( $status->isGood() ) - wfRunHooks( 'UploadComplete', array( &$this ) ); - - return $status; - } - - /** - * Returns a title or null - */ - function getTitle() { - if ( $this->mTitle !== false ) - return $this->mTitle; - - /** - * Chop off any directories in the given filename. Then - * filter out illegal characters, and try to make a legible name - * out of it. We'll strip some silently that Title would die on. - */ - - $basename = $this->mDesiredDestName; - - $this->mFilteredName = wfStripIllegalFilenameChars( $basename ); - - /** - * We'll want to blacklist against *any* 'extension', and use - * only the final one for the whitelist. - */ - list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); - - if( count( $ext ) ) { - $this->mFinalExtension = $ext[count( $ext ) - 1]; - } else { - $this->mFinalExtension = ''; - } - - /* Don't allow users to override the blacklist (check file extension) */ - global $wgCheckFileExtensions, $wgStrictFileExtensions; - global $wgFileExtensions, $wgFileBlacklist; - if ( $this->mFinalExtension == '' ) { - $this->mTitleError = self::FILETYPE_MISSING; - return $this->mTitle = null; - } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) || - ( $wgCheckFileExtensions && $wgStrictFileExtensions && - !$this->checkFileExtension( $this->mFinalExtension, $wgFileExtensions ) ) ) { - $this->mTitleError = self::FILETYPE_BADTYPE; - return $this->mTitle = null; - } - - # If there was more than one "extension", reassemble the base - # filename to prevent bogus complaints about length - if( count( $ext ) > 1 ) { - for( $i = 0; $i < count( $ext ) - 1; $i++ ) - $partname .= '.' . $ext[$i]; - } - - if( strlen( $partname ) < 1 ) { - $this->mTitleError = self::MIN_LENGTH_PARTNAME; - return $this->mTitle = null; - } - - $nt = Title::makeTitleSafe( NS_FILE, $this->mFilteredName ); - if( is_null( $nt ) ) { - $this->mTitleError = self::ILLEGAL_FILENAME; - return $this->mTitle = null; - } - return $this->mTitle = $nt; - } - - function getLocalFile() { - if( is_null( $this->mLocalFile ) ) { - $nt = $this->getTitle(); - $this->mLocalFile = is_null( $nt ) ? null : wfLocalFile( $nt ); - } - return $this->mLocalFile; - } - - /** - * Stash a file in a temporary directory for later processing - * after the user has confirmed it. - * - * If the user doesn't explicitly cancel or accept, these files - * can accumulate in the temp directory. - * - * @param string $saveName - the destination filename - * @param string $tempName - the source temporary file to save - * @return string - full path the stashed file, or false on failure - * @access private - */ - function saveTempUploadedFile( $saveName, $tempName ) { - global $wgOut; - $repo = RepoGroup::singleton()->getLocalRepo(); - $status = $repo->storeTemp( $saveName, $tempName ); - return $status; - } - - /** - * Stash a file in a temporary directory for later processing, - * and save the necessary descriptive info into the session. - * Returns a key value which will be passed through a form - * to pick up the path info on a later invocation. - * - * @return int - * @access private - */ - function stashSession() { - $status = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath ); - - if( !$status->isGood() ) { - # Couldn't save the file. - return false; - } - - return array( - 'mTempPath' => $status->value, - 'mFileSize' => $this->mFileSize, - 'mFileProps' => $this->mFileProps, - 'version' => self::SESSION_VERSION, - ); - } - - /** - * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @return success - */ - function unsaveUploadedFile() { - $repo = RepoGroup::singleton()->getLocalRepo(); - $success = $repo->freeTemp( $this->mTempPath ); - return $success; - } - - /** - * If we've modified the upload file we need to manually remove it - * on exit to clean up. - * @access private - */ - function cleanupTempFile() { - if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) { - wfDebug( __METHOD__.": Removing temporary file {$this->mTempPath}\n" ); - unlink( $this->mTempPath ); - } - } - - function getTempPath() { - return $this->mTempPath; - } - - - /** - * Split a file into a base name and all dot-delimited 'extensions' - * on the end. Some web server configurations will fall back to - * earlier pseudo-'extensions' to determine type and execute - * scripts, so the blacklist needs to check them all. - * - * @return array - */ - function splitExtensions( $filename ) { - $bits = explode( '.', $filename ); - $basename = array_shift( $bits ); - return array( $basename, $bits ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if the extension is in the list. - * - * @param string $ext - * @param array $list - * @return bool - */ - function checkFileExtension( $ext, $list ) { - return in_array( strtolower( $ext ), $list ); - } - - /** - * Perform case-insensitive match against a list of file extensions. - * Returns true if any of the extensions are in the list. - * - * @param array $ext - * @param array $list - * @return bool - */ - function checkFileExtensionList( $ext, $list ) { - foreach( $ext as $e ) { - if( in_array( strtolower( $e ), $list ) ) { - return true; - } - } - return false; - } - - - /** - * Checks if the mime type of the uploaded file matches the file extension. - * - * @param string $mime the mime type of the uploaded file - * @param string $extension The filename extension that the file is to be served with - * @return bool - */ - public static function verifyExtension( $mime, $extension ) { - $magic = MimeMagic::singleton(); - - if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' ) - if ( ! $magic->isRecognizableExtension( $extension ) ) { - wfDebug( __METHOD__.": passing file with unknown detected mime type; " . - "unrecognized extension '$extension', can't verify\n" ); - return true; - } else { - wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ". - "recognized extension '$extension', so probably invalid file\n" ); - return false; - } - - $match= $magic->isMatchingExtension($extension,$mime); - - if ($match===NULL) { - wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" ); - return true; - } elseif ($match===true) { - wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" ); - - #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it! - return true; - - } else { - wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" ); - return false; - } - } - - /** - * Heuristic for detecting files that *could* contain JavaScript instructions or - * things that may look like HTML to a browser and are thus - * potentially harmful. The present implementation will produce false positives in some situations. - * - * @param string $file Pathname to the temporary upload file - * @param string $mime The mime type of the file - * @param string $extension The extension of the file - * @return bool true if the file contains something looking like embedded scripts - */ - function detectScript($file, $mime, $extension) { - global $wgAllowTitlesInSVG; - - #ugly hack: for text files, always look at the entire file. - #For binary field, just check the first K. - - if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file ); - else { - $fp = fopen( $file, 'rb' ); - $chunk = fread( $fp, 1024 ); - fclose( $fp ); - } - - $chunk= strtolower( $chunk ); - - if (!$chunk) return false; - - #decode from UTF-16 if needed (could be used for obfuscation). - if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE"; - elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE"; - else $enc= NULL; - - if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk); - - $chunk= trim($chunk); - - #FIXME: convert from UTF-16 if necessarry! - - wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n"); - - #check for HTML doctype - if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true; - - /** - * Internet Explorer for Windows performs some really stupid file type - * autodetection which can cause it to interpret valid image files as HTML - * and potentially execute JavaScript, creating a cross-site scripting - * attack vectors. - * - * Apple's Safari browser also performs some unsafe file type autodetection - * which can cause legitimate files to be interpreted as HTML if the - * web server is not correctly configured to send the right content-type - * (or if you're really uploading plain text and octet streams!) - * - * Returns true if IE is likely to mistake the given file for HTML. - * Also returns true if Safari would mistake the given file for HTML - * when served with a generic content-type. - */ - - $tags = array( - '<body', - '<head', - '<html', #also in safari - '<img', - '<pre', - '<script', #also in safari - '<table' - ); - if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) { - $tags[] = '<title'; - } - - foreach( $tags as $tag ) { - if( false !== strpos( $chunk, $tag ) ) { - return true; - } - } - - /* - * look for javascript - */ - - #resolve entity-refs to look at attributes. may be harsh on big files... cache result? - $chunk = Sanitizer::decodeCharReferences( $chunk ); - - #look for script-types - if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true; - - #look for html-style script-urls - if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; - - #look for css-style script-urls - if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true; - - wfDebug("SpecialUpload::detectScript: no scripts found\n"); - return false; - } - - /** - * Generic wrapper function for a virus scanner program. - * This relies on the $wgAntivirus and $wgAntivirusSetup variables. - * $wgAntivirusRequired may be used to deny upload if the scan fails. - * - * @param string $file Pathname to the temporary upload file - * @return mixed false if not virus is found, NULL if the scan fails or is disabled, - * or a string containing feedback from the virus scanner if a virus was found. - * If textual feedback is missing but a virus was found, this function returns true. - */ - function detectVirus($file) { - global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut; - - if ( !$wgAntivirus ) { - wfDebug( __METHOD__.": virus scanner disabled\n"); - return NULL; - } - - if ( !$wgAntivirusSetup[$wgAntivirus] ) { - wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" ); - $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) ); - return wfMsg('virus-unknownscanner') . " $wgAntivirus"; - } - - # look up scanner configuration - $command = $wgAntivirusSetup[$wgAntivirus]["command"]; - $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"]; - $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ? - $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null; - - if ( strpos( $command,"%f" ) === false ) { - # simple pattern: append file to scan - $command .= " " . wfEscapeShellArg( $file ); - } else { - # complex pattern: replace "%f" with file to scan - $command = str_replace( "%f", wfEscapeShellArg( $file ), $command ); - } - - wfDebug( __METHOD__.": running virus scan: $command \n" ); - - # execute virus scanner - $exitCode = false; - - #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too. - # that does not seem to be worth the pain. - # Ask me (Duesentrieb) about it if it's ever needed. - $output = array(); - if ( wfIsWindows() ) { - exec( "$command", $output, $exitCode ); - } else { - exec( "$command 2>&1", $output, $exitCode ); - } - - # map exit code to AV_xxx constants. - $mappedCode = $exitCode; - if ( $exitCodeMap ) { - if ( isset( $exitCodeMap[$exitCode] ) ) { - $mappedCode = $exitCodeMap[$exitCode]; - } elseif ( isset( $exitCodeMap["*"] ) ) { - $mappedCode = $exitCodeMap["*"]; - } - } - - if ( $mappedCode === AV_SCAN_FAILED ) { - # scan failed (code was mapped to false by $exitCodeMap) - wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" ); - - if ( $wgAntivirusRequired ) { - return wfMsg('virus-scanfailed', array( $exitCode ) ); - } else { - return NULL; - } - } else if ( $mappedCode === AV_SCAN_ABORTED ) { - # scan failed because filetype is unknown (probably imune) - wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" ); - return NULL; - } else if ( $mappedCode === AV_NO_VIRUS ) { - # no virus found - wfDebug( __METHOD__.": file passed virus scan.\n" ); - return false; - } else { - $output = join( "\n", $output ); - $output = trim( $output ); - - if ( !$output ) { - $output = true; #if there's no output, return true - } elseif ( $msgPattern ) { - $groups = array(); - if ( preg_match( $msgPattern, $output, $groups ) ) { - if ( $groups[1] ) { - $output = $groups[1]; - } - } - } - - wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" ); - return $output; - } - } - - /** - * Check if the temporary file is MacBinary-encoded, as some uploads - * from Internet Explorer on Mac OS Classic and Mac OS X will be. - * If so, the data fork will be extracted to a second temporary file, - * which will then be checked for validity and either kept or discarded. - * - * @access private - */ - function checkMacBinary() { - $macbin = new MacBinary( $this->mTempPath ); - if( $macbin->isValid() ) { - $dataFile = tempnam( wfTempDir(), "WikiMacBinary" ); - $dataHandle = fopen( $dataFile, 'wb' ); - - wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" ); - $macbin->extractData( $dataHandle ); - - $this->mTempPath = $dataFile; - $this->mFileSize = $macbin->dataForkLength(); - - // We'll have to manually remove the new file if it's not kept. - $this->mRemoveTempFile = true; - } - $macbin->close(); - } - - /** - * Check if there's an overwrite conflict and, if so, if restrictions - * forbid this user from performing the upload. - * - * @return mixed true on success, WikiError on failure - * @access private - */ - function checkOverwrite() { - global $wgUser; - // First check whether the local file can be overwritten - if( $this->mLocalFile->exists() ) - if( !self::userCanReUpload( $wgUser, $this->mLocalFile ) ) - return 'fileexists-forbidden'; - - // Check shared conflicts - $file = wfFindFile( $this->mLocalFile->getName() ); - if ( $file && ( !$wgUser->isAllowed( 'reupload' ) || - !$wgUser->isAllowed( 'reupload-shared' ) ) ) - return 'fileexists-shared-forbidden'; - - return true; - - } - - /** - * Check if a user is the last uploader - * - * @param User $user - * @param string $img, image name - * @return bool - */ - public static function userCanReUpload( User $user, $img ) { - if( $user->isAllowed( 'reupload' ) ) - return true; // non-conditional - if( !$user->isAllowed( 'reupload-own' ) ) - return false; - if( is_string( $img ) ) - $img = wfLocalFile( $img ); - if ( !( $img instanceof LocalFile ) ) - return false; - - return $user->getId() == $img->getUser( 'id' ); - } - - public static function getExistsWarning( $file ) { - if( $file->exists() ) - return array( 'exists', $file ); - - if( $file->getTitle()->getArticleID() ) - return array( 'page-exists', $file ); - - if( strpos( $file->getName(), '.' ) == false ) { - $partname = $file->getName(); - $rawExtension = ''; - } else { - $n = strrpos( $file->getName(), '.' ); - $rawExtension = substr( $file->getName(), $n + 1 ); - $partname = substr( $file->getName(), 0, $n ); - } - - if ( $rawExtension != $file->getExtension() ) { - // We're not using the normalized form of the extension. - // Normal form is lowercase, using most common of alternate - // extensions (eg 'jpg' rather than 'JPEG'). - // - // Check for another file using the normalized form... - $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() ); - $file_lc = wfLocalFile( $nt_lc ); - - if( $file_lc->exists() ) - return array( 'exists-normalized', $file_lc ); - } - - if ( self::isThumbName( $file->getName() ) ) { - # Check for filenames like 50px- or 180px-, these are mostly thumbnails - $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension ); - $file_thb = wfLocalFile( $nt_thb ); - if( $file_thb->exists() ) - return array( 'thumb', $file_thb ); - } - - return false; - } - - public static function isThumbName( $filename ) { - $n = strrpos( $filename, '.' ); - $partname = $n ? substr( $filename, 0, $n ) : $filename; - return ( - substr( $partname , 3, 3 ) == 'px-' || - substr( $partname , 2, 3 ) == 'px-' - ) && - ereg( "[0-9]{2}" , substr( $partname , 0, 2) ); - } - - /** - * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]] - * - * @return array list of prefixes - */ - public static function getFilenamePrefixBlacklist() { - $blacklist = array(); - $message = wfMsgForContent( 'filename-prefix-blacklist' ); - if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) { - $lines = explode( "\n", $message ); - foreach( $lines as $line ) { - // Remove comment lines - $comment = substr( trim( $line ), 0, 1 ); - if ( $comment == '#' || $comment == '' ) { - continue; - } - // Remove additional comments after a prefix - $comment = strpos( $line, '#' ); - if ( $comment > 0 ) { - $line = substr( $line, 0, $comment-1 ); - } - $blacklist[] = trim( $line ); - } - } - return $blacklist; - } - - -} diff --git a/includes/UploadFromStash.php b/includes/UploadFromStash.php deleted file mode 100644 index 8bff3b49..00000000 --- a/includes/UploadFromStash.php +++ /dev/null @@ -1,58 +0,0 @@ -<?php - -class UploadFromStash extends UploadBase { - static function isValidSessionKey( $key, $sessionData ) { - return !empty( $key ) && - is_array( $sessionData ) && - isset( $sessionData[$key] ) && - isset( $sessionData[$key]['version'] ) && - $sessionData[$key]['version'] == self::SESSION_VERSION - ; - } - static function isValidRequest( $request ) { - $sessionData = $request->getSessionData('wsUploadData'); - return self::isValidSessionKey( - $request->getInt( 'wpSessionKey' ), - $sessionData - ); - } - - function initialize( $name, $sessionData ) { - /** - * Confirming a temporarily stashed upload. - * We don't want path names to be forged, so we keep - * them in the session on the server and just give - * an opaque key to the user agent. - */ - $this->initialize( $name, - $sessionData['mTempPath'], - $sessionData['mFileSize'], - false - ); - - $this->mFileProps = $sessionData['mFileProps']; - } - function initializeFromRequest( &$request ) { - $sessionKey = $request->getInt( 'wpSessionKey' ); - $sessionData = $request->getSessionData('wsUploadData'); - - $desiredDestName = $request->getText( 'wpDestFile' ); - if( !$desiredDestName ) - $desiredDestName = $request->getText( 'wpUploadFile' ); - - return $this->initialize( $desiredDestName, $sessionData[$sessionKey] ); - } - - /** - * File has been previously verified so no need to do so again. - */ - protected function verifyFile( $tmpfile ) { - return true; - } - /** - * We're here from "ignore warnings anyway" so return just OK - */ - function checkWarnings() { - return array(); - } -} diff --git a/includes/UploadFromUpload.php b/includes/UploadFromUpload.php deleted file mode 100644 index 1b6762c6..00000000 --- a/includes/UploadFromUpload.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php - -class UploadFromUpload extends UploadBase { - - function initializeFromRequest( &$request ) { - $desiredDestName = $request->getText( 'wpDestFile' ); - if( !$desiredDestName ) - $desiredDestName = $request->getText( 'wpUploadFile' ); - - return $this->initialize( - $desiredDestName, - $request->getFileTempName( 'wpUploadFile' ), - $request->getFileSize( 'wpUploadFile' ) - ); - } - - static function isValidRequest( $request ) { - return (bool)$request->getFileTempName( 'wpUploadFile' ); - } -} diff --git a/includes/UploadFromUrl.php b/includes/UploadFromUrl.php deleted file mode 100644 index 7e23b8cd..00000000 --- a/includes/UploadFromUrl.php +++ /dev/null @@ -1,92 +0,0 @@ -<?php - - -class UploadFromUrl extends UploadBase { - static function isAllowed( $user ) { - if( !$user->isAllowed( 'upload_by_url' ) ) - return 'upload_by_url'; - return parent::isAllowed( $user ); - } - static function isEnabled() { - global $wgAllowCopyUploads; - return $wgAllowCopyUploads && parent::isEnabled(); - } - - function initialize( $name, $url ) { - global $wgTmpDirectory; - $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' ); - $this-initialize( $name, $local_file, 0, true ); - - $this->mUrl = trim( $url ); - } - - /** - * Do the real fetching stuff - */ - function fetchFile() { - if( stripos($this->mUrl, 'http://') !== 0 && stripos($this->mUrl, 'ftp://') !== 0 ) { - return array( - 'status' => self::BEFORE_PROCESSING, - 'error' => 'upload-proto-error', - ); - } - $res = $this->curlCopy(); - if( $res !== true ) { - return array( - 'status' => self::BEFORE_PROCESSING, - 'error' => $res, - ); - } - return self::OK; - } - - /** - * Safe copy from URL - * Returns true if there was an error, false otherwise - */ - private function curlCopy() { - global $wgUser, $wgOut; - - # Open temporary file - $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" ); - if( $this->mCurlDestHandle === false ) { - # Could not open temporary file to write in - return 'upload-file-error'; - } - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug - curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout - curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed - curl_setopt( $ch, CURLOPT_URL, $this->mUrl); - curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); - curl_exec( $ch ); - $error = curl_errno( $ch ); - curl_close( $ch ); - - fclose( $this->mCurlDestHandle ); - unset( $this->mCurlDestHandle ); - - if( $error ) - return "upload-curl-error$errornum"; - - return true; - } - - /** - * Callback function for CURL-based web transfer - * Write data to file unless we've passed the length limit; - * if so, abort immediately. - * @access private - */ - function uploadCurlCallback( $ch, $data ) { - global $wgMaxUploadSize; - $length = strlen( $data ); - $this->mFileSize += $length; - if( $this->mFileSize > $wgMaxUploadSize ) { - return 0; - } - fwrite( $this->mCurlDestHandle, $data ); - return $length; - } -} diff --git a/includes/User.php b/includes/User.php index 9fee089c..cc861ad4 100644 --- a/includes/User.php +++ b/includes/User.php @@ -141,9 +141,11 @@ class User { 'createtalk', 'delete', 'deletedhistory', + 'deleterevision', 'edit', 'editinterface', 'editusercssjs', + 'hideuser', 'import', 'importupload', 'ipblock-exempt', @@ -155,6 +157,7 @@ class User { 'move-subpages', 'nominornewtalk', 'noratelimit', + 'override-export-depth', 'patrol', 'protect', 'proxyunbannable', @@ -164,13 +167,17 @@ class User { 'reupload-shared', 'rollback', 'siteadmin', + 'suppressionlog', 'suppressredirect', + 'suppressrevision', 'trackback', 'undelete', 'unwatchedpages', 'upload', 'upload_by_url', 'userrights', + 'userrights-interwiki', + 'writeapi', ); /** * \string Cached results of getAllRights() @@ -581,11 +588,12 @@ class User { * @return \bool True or false */ static function isCreatableName( $name ) { + global $wgInvalidUsernameCharacters; return self::isUsableName( $name ) && // Registration-time character blacklisting... - strpos( $name, '@' ) === false; + !preg_match( '/[' . preg_quote( $wgInvalidUsernameCharacters, '/' ) . ']/', $name ); } /** @@ -886,6 +894,8 @@ class User { $dbr = wfGetDB( DB_MASTER ); $s = $dbr->selectRow( 'user', '*', array( 'user_id' => $this->mId ), __METHOD__ ); + wfRunHooks( 'UserLoadFromDatabase', array( $this, &$s ) ); + if ( $s !== false ) { # Initialise user table data $this->loadFromRow( $s ); @@ -909,7 +919,7 @@ class User { $this->mDataLoaded = true; if ( isset( $row->user_id ) ) { - $this->mId = $row->user_id; + $this->mId = intval( $row->user_id ); } $this->mName = $row->user_name; $this->mRealName = $row->user_real_name; @@ -1013,9 +1023,14 @@ class User { * @return \type{\arrayof{\string}} Array of user toggle names */ static function getToggles() { - global $wgContLang; + global $wgContLang, $wgUseRCPatrol; $extraToggles = array(); wfRunHooks( 'UserToggles', array( &$extraToggles ) ); + if( $wgUseRCPatrol ) { + $extraToggles[] = 'hidepatrolled'; + $extraToggles[] = 'newpageshidepatrolled'; + $extraToggles[] = 'watchlisthidepatrolled'; + } return array_merge( self::$mToggles, $extraToggles, $wgContLang->getExtraUserToggles() ); } @@ -1149,10 +1164,17 @@ class User { */ public function isPingLimitable() { global $wgRateLimitsExcludedGroups; + global $wgRateLimitsExcludedIPs; if( array_intersect( $this->getEffectiveGroups(), $wgRateLimitsExcludedGroups ) ) { // Deprecated, but kept for backwards-compatibility config return false; } + if( in_array( wfGetIP(), $wgRateLimitsExcludedIPs ) ) { + // No other good way currently to disable rate limits + // for specific IPs. :P + // But this is a crappy hack and should die. + return false; + } return !$this->isAllowed('noratelimit'); } @@ -1309,6 +1331,15 @@ class User { } /** + * If user is blocked, return the ID for the block + * @return \int Block ID + */ + function getBlockId() { + $this->getBlockedStatus(); + return ($this->mBlock ? $this->mBlock->mId : false); + } + + /** * Check if user is blocked on all wikis. * Do not use for actual edit permission checks! * This is intented for quick UI checks. @@ -1909,6 +1940,13 @@ class User { } $this->mOptions[$oname] = $val; } + + /** + * Reset all options to the site defaults + */ + function restoreOptions() { + $this->mOptions = User::getDefaultOptions(); + } /** * Get the user's preferred date format. @@ -1983,7 +2021,7 @@ class User { * @return \int User'e edit count */ function getEditCount() { - if ($this->mId) { + if ($this->getId()) { if ( !isset( $this->mEditCount ) ) { /* Populate the count, if it has not been populated yet */ $this->mEditCount = User::edits($this->mId); @@ -2073,11 +2111,15 @@ class User { * @param $action \string action to be checked * @return \bool True if action is allowed, else false */ - function isAllowed($action='') { + function isAllowed( $action = '' ) { if ( $action === '' ) - // In the spirit of DWIM - return true; - + return true; // In the spirit of DWIM + # Patrolling may not be enabled + if( $action === 'patrol' || $action === 'autopatrol' ) { + global $wgUseRCPatrol, $wgUseNPPatrol; + if( !$wgUseRCPatrol && !$wgUseNPPatrol ) + return false; + } # Use strict parameter to avoid matching numeric 0 accidentally inserted # by misconfiguration: 0 == 'foo' return in_array( $action, $this->getRights(), true ); @@ -2705,7 +2747,14 @@ class User { * @return \bool True if matches, false otherwise */ function checkTemporaryPassword( $plaintext ) { - return self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ); + global $wgNewPasswordExpiry; + if( self::comparePasswords( $this->mNewpassword, $plaintext, $this->getId() ) ) { + $this->load(); + $expiry = wfTimestamp( TS_UNIX, $this->mNewpassTime ) + $wgNewPasswordExpiry; + return ( time() < $expiry ); + } else { + return false; + } } /** diff --git a/includes/UserArray.php b/includes/UserArray.php index a2f54b7f..d48a4440 100644 --- a/includes/UserArray.php +++ b/includes/UserArray.php @@ -12,6 +12,17 @@ abstract class UserArray implements Iterator { return $userArray; } + static function newFromIDs( $ids ) { + $ids = array_map( 'intval', (array)$ids ); // paranoia + if ( !$ids ) + // Database::select() doesn't like empty arrays + return new ArrayIterator(array()); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'user', '*', array( 'user_id' => $ids ), + __METHOD__ ); + return self::newFromResult( $res ); + } + protected static function newFromResult_internal( $res ) { $userArray = new UserArrayFromResult( $res ); return $userArray; diff --git a/includes/UserMailer.php b/includes/UserMailer.php index ab1a740b..b6484935 100644 --- a/includes/UserMailer.php +++ b/includes/UserMailer.php @@ -40,7 +40,7 @@ class MailAddress { } else { $this->address = strval( $address ); $this->name = strval( $name ); - $this->reaName = strval( $realName ); + $this->realName = strval( $realName ); } } @@ -101,7 +101,7 @@ class UserMailer { * @param $from MailAddress: sender's email * @param $subject String: email's subject. * @param $body String: email's text. - * @param $replyto String: optional reply-to email (default: null). + * @param $replyto MailAddress: optional reply-to email (default: null). * @param $contentType String: optional custom Content-Type * @return mixed True on success, a WikiError object on failure. */ @@ -281,11 +281,44 @@ class EmailNotification { * @param $oldid (default: false) */ function notifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid = false) { - global $wgEnotifUseJobQ; + global $wgEnotifUseJobQ, $wgEnotifWatchlist, $wgShowUpdatedMarker; - if( $title->getNamespace() < 0 ) + if ($title->getNamespace() < 0) return; + // Build a list of users to notfiy + $watchers = array(); + if ($wgEnotifWatchlist || $wgShowUpdatedMarker) { + $dbw = wfGetDB( DB_MASTER ); + $res = $dbw->select( array( 'watchlist' ), + array( 'wl_user' ), + array( + 'wl_title' => $title->getDBkey(), + 'wl_namespace' => $title->getNamespace(), + 'wl_user != ' . intval( $editor->getID() ), + 'wl_notificationtimestamp IS NULL', + ), __METHOD__ + ); + while ($row = $dbw->fetchObject( $res ) ) { + $watchers[] = intval( $row->wl_user ); + } + if ($watchers) { + // Update wl_notificationtimestamp for all watching users except + // the editor + $dbw->begin(); + $dbw->update( 'watchlist', + array( /* SET */ + 'wl_notificationtimestamp' => $dbw->timestamp( $timestamp ) + ), array( /* WHERE */ + 'wl_title' => $title->getDBkey(), + 'wl_namespace' => $title->getNamespace(), + 'wl_user' => $watchers + ), __METHOD__ + ); + $dbw->commit(); + } + } + if ($wgEnotifUseJobQ) { $params = array( "editor" => $editor->getName(), @@ -293,11 +326,12 @@ class EmailNotification { "timestamp" => $timestamp, "summary" => $summary, "minorEdit" => $minorEdit, - "oldid" => $oldid); + "oldid" => $oldid, + "watchers" => $watchers); $job = new EnotifNotifyJob( $title, $params ); $job->insert(); } else { - $this->actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid); + $this->actuallyNotifyOnPageChange( $editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers ); } } @@ -310,16 +344,16 @@ class EmailNotification { * * @param $editor User object * @param $title Title object - * @param $timestamp - * @param $summary - * @param $minorEdit - * @param $oldid (default: false) + * @param $timestamp string Edit timestamp + * @param $summary string Edit summary + * @param $minorEdit bool + * @param $oldid int Revision ID + * @param $watchers array of user IDs */ - function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid=false) { - + function actuallyNotifyOnPageChange($editor, $title, $timestamp, $summary, $minorEdit, $oldid, $watchers) { # we use $wgPasswordSender as sender's address global $wgEnotifWatchlist; - global $wgEnotifMinorEdits, $wgEnotifUserTalk, $wgShowUpdatedMarker; + global $wgEnotifMinorEdits, $wgEnotifUserTalk; global $wgEnotifImpersonal; wfProfileIn( __METHOD__ ); @@ -364,30 +398,12 @@ class EmailNotification { if ( $wgEnotifWatchlist ) { // Send updates to watchers other than the current editor - $userCondition = 'wl_user != ' . $editor->getID(); - if ( $userTalkId !== false ) { - // Already sent an email to this person - $userCondition .= ' AND wl_user != ' . intval( $userTalkId ); - } - $dbr = wfGetDB( DB_SLAVE ); - - list( $user ) = $dbr->tableNamesN( 'user' ); - - $res = $dbr->select( array( 'watchlist', 'user' ), - array( "$user.*" ), - array( - 'wl_user=user_id', - 'wl_title' => $title->getDBkey(), - 'wl_namespace' => $title->getNamespace(), - $userCondition, - 'wl_notificationtimestamp IS NULL', - ), __METHOD__ ); - $userArray = UserArray::newFromResult( $res ); - + $userArray = UserArray::newFromIDs( $watchers ); foreach ( $userArray as $watchingUser ) { if ( $watchingUser->getOption( 'enotifwatchlistpages' ) && ( !$minorEdit || $watchingUser->getOption('enotifminoredits') ) && - $watchingUser->isEmailConfirmed() ) + $watchingUser->isEmailConfirmed() && + $watchingUser->getID() != $userTalkId ) { $this->compose( $watchingUser ); } @@ -402,28 +418,8 @@ class EmailNotification { } $this->sendMails(); - - $latestTimestamp = Revision::getTimestampFromId( $title, $title->getLatestRevID() ); - // Do not update watchlists if something else already did. - if ( $timestamp >= $latestTimestamp && ($wgShowUpdatedMarker || $wgEnotifWatchlist) ) { - # Mark the changed watch-listed page with a timestamp, so that the page is - # listed with an "updated since your last visit" icon in the watch list. Do - # not do this to users for their own edits. - $dbw = wfGetDB( DB_MASTER ); - $dbw->update( 'watchlist', - array( /* SET */ - 'wl_notificationtimestamp' => $dbw->timestamp($timestamp) - ), array( /* WHERE */ - 'wl_title' => $title->getDBkey(), - 'wl_namespace' => $title->getNamespace(), - 'wl_notificationtimestamp IS NULL', - 'wl_user != ' . $editor->getID() - ), __METHOD__ - ); - } - wfProfileOut( __METHOD__ ); - } # function NotifyOnChange + } /** * @private @@ -563,7 +559,7 @@ class EmailNotification { * @private */ function sendPersonalised( $watchingUser ) { - global $wgLang, $wgEnotifUseRealName; + global $wgContLang, $wgEnotifUseRealName; // From the PHP manual: // Note: The to parameter cannot be an address in the form of "Something <someone@example.com>". // The mail command will not parse this properly while talking with the MTA. @@ -577,7 +573,7 @@ class EmailNotification { # expressed in terms of individual local time of the notification # recipient, i.e. watching user $body = str_replace('$PAGEEDITDATE', - $wgLang->timeanddate( $this->timestamp, true, false, $timecorrection ), + $wgContLang->timeanddate( $this->timestamp, true, false, $timecorrection ), $body); return UserMailer::send($to, $this->from, $this->subject, $body, $this->replyto); @@ -588,7 +584,7 @@ class EmailNotification { * mailing. Takes an array of MailAddress objects. */ function sendImpersonal( $addresses ) { - global $wgLang; + global $wgContLang; if (empty($addresses)) return; @@ -597,7 +593,7 @@ class EmailNotification { array( '$WATCHINGUSERNAME', '$PAGEEDITDATE'), array( wfMsgForContent('enotif_impersonal_salutation'), - $wgLang->timeanddate($this->timestamp, true, false, false)), + $wgContLang->timeanddate($this->timestamp, true, false, false)), $this->body); return UserMailer::send($addresses, $this->from, $this->subject, $body, $this->replyto); diff --git a/includes/WatchedItem.php b/includes/WatchedItem.php index 2d2d34f1..a2c1f036 100644 --- a/includes/WatchedItem.php +++ b/includes/WatchedItem.php @@ -38,11 +38,10 @@ class WatchedItem { public function isWatched() { # Pages and their talk pages are considered equivalent for watching; # remember that talk namespaces are numbered as page namespace+1. - $fname = 'WatchedItem::isWatched'; $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'watchlist', 1, array( 'wl_user' => $this->id, 'wl_namespace' => $this->ns, - 'wl_title' => $this->ti ), $fname ); + 'wl_title' => $this->ti ), __METHOD__ ); $iswatched = ($dbr->numRows( $res ) > 0) ? 1 : 0; return $iswatched; } @@ -53,31 +52,30 @@ class WatchedItem { * @return bool (always true) */ public function addWatch() { - $fname = 'WatchedItem::addWatch'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); // Use INSERT IGNORE to avoid overwriting the notification timestamp // if there's already an entry for this page $dbw = wfGetDB( DB_MASTER ); $dbw->insert( 'watchlist', array( - 'wl_user' => $this->id, - 'wl_namespace' => ($this->ns & ~1), + 'wl_user' => $this->id, + 'wl_namespace' => MWNamespace::getSubject($this->ns), 'wl_title' => $this->ti, 'wl_notificationtimestamp' => NULL - ), $fname, 'IGNORE' ); + ), __METHOD__, 'IGNORE' ); // Every single watched page needs now to be listed in watchlist; // namespace:page and namespace_talk:page need separate entries: $dbw->insert( 'watchlist', array( 'wl_user' => $this->id, - 'wl_namespace' => ($this->ns | 1 ), + 'wl_namespace' => MWNamespace::getTalk($this->ns), 'wl_title' => $this->ti, 'wl_notificationtimestamp' => NULL - ), $fname, 'IGNORE' ); + ), __METHOD__, 'IGNORE' ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return true; } @@ -86,16 +84,14 @@ class WatchedItem { * @return bool */ public function removeWatch() { - $fname = 'WatchedItem::removeWatch'; - $success = false; $dbw = wfGetDB( DB_MASTER ); $dbw->delete( 'watchlist', array( 'wl_user' => $this->id, - 'wl_namespace' => ($this->ns & ~1), + 'wl_namespace' => MWNamespace::getSubject($this->ns), 'wl_title' => $this->ti - ), $fname + ), __METHOD__ ); if ( $dbw->affectedRows() ) { $success = true; @@ -108,9 +104,9 @@ class WatchedItem { $dbw->delete( 'watchlist', array( 'wl_user' => $this->id, - 'wl_namespace' => ($this->ns | 1), + 'wl_namespace' => MWNamespace::getTalk($this->ns), 'wl_title' => $this->ti - ), $fname + ), __METHOD__ ); if ( $dbw->affectedRows() ) { @@ -134,8 +130,7 @@ class WatchedItem { /** * Handle duplicate entries. Backend for duplicateEntries(). */ - private static function doDuplicateEntries( $ot, $nt ) { - $fname = "WatchedItem::duplicateEntries"; + private static function doDuplicateEntries( $ot, $nt ) { $oldnamespace = $ot->getNamespace(); $newnamespace = $nt->getNamespace(); $oldtitle = $ot->getDBkey(); @@ -144,7 +139,7 @@ class WatchedItem { $dbw = wfGetDB( DB_MASTER ); $res = $dbw->select( 'watchlist', 'wl_user', array( 'wl_namespace' => $oldnamespace, 'wl_title' => $oldtitle ), - $fname, 'FOR UPDATE' + __METHOD__, 'FOR UPDATE' ); # Construct array to replace into the watchlist $values = array(); @@ -165,7 +160,7 @@ class WatchedItem { # Perform replace # Note that multi-row replace is very efficient for MySQL but may be inefficient for # some other DBMSes, mostly due to poor simulation by us - $dbw->replace( 'watchlist', array(array( 'wl_user', 'wl_namespace', 'wl_title')), $values, $fname ); + $dbw->replace( 'watchlist', array( array( 'wl_user', 'wl_namespace', 'wl_title' ) ), $values, __METHOD__ ); return true; } } diff --git a/includes/WatchlistEditor.php b/includes/WatchlistEditor.php index e49851bd..82f62f6a 100644 --- a/includes/WatchlistEditor.php +++ b/includes/WatchlistEditor.php @@ -407,6 +407,8 @@ class WatchlistEditor { * @return string */ private function buildRemoveLine( $title, $redirect, $skin ) { + global $wgLang; + $link = $skin->makeLinkObj( $title ); if( $redirect ) $link = '<span class="watchlistredir">' . $link . '</span>'; @@ -419,7 +421,7 @@ class WatchlistEditor { } return "<li>" . Xml::check( 'titles[]', false, array( 'value' => $title->getPrefixedText() ) ) - . $link . " (" . implode( ' | ', $tools ) . ")" . "</li>\n"; + . $link . " (" . $wgLang->pipeList( $tools ) . ")" . "</li>\n"; } /** @@ -480,11 +482,13 @@ class WatchlistEditor { * @return string */ public static function buildTools( $skin ) { + global $wgLang; + $tools = array(); $modes = array( 'view' => false, 'edit' => 'edit', 'raw' => 'raw' ); foreach( $modes as $mode => $subpage ) { $tools[] = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Watchlist', $subpage ), wfMsgHtml( "watchlisttools-{$mode}" ) ); } - return implode( ' | ', $tools ); + return $wgLang->pipeList( $tools ); } } diff --git a/includes/WebRequest.php b/includes/WebRequest.php index 46747125..0928e4d5 100644 --- a/includes/WebRequest.php +++ b/includes/WebRequest.php @@ -231,6 +231,7 @@ class WebRequest { $data = $this->normalizeUnicode( $data ); return $data; } else { + taint( $default ); return $default; } } @@ -251,7 +252,7 @@ class WebRequest { $val = $default; } if( is_null( $val ) ) { - return null; + return $val; } else { return (string)$val; } diff --git a/includes/Wiki.php b/includes/Wiki.php index ce4ce67e..38f19c96 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -115,12 +115,15 @@ class MediaWiki { if( count( $wgContLang->getVariants() ) > 1 && !is_null( $ret ) && $ret->getArticleID() == 0 ) $wgContLang->findVariantLink( $title, $ret ); } - if( ( $oldid = $wgRequest->getInt( 'oldid' ) ) - && ( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) ) { - // Allow oldid to override a changed or missing title. - $rev = Revision::newFromId( $oldid ); - if( $rev ) { - $ret = $rev->getTitle(); + # For non-special titles, check for implicit titles + if( is_null( $ret ) || $ret->getNamespace() != NS_SPECIAL ) { + // We can have urls with just ?diff=,?oldid= or even just ?diff= + $oldid = $wgRequest->getInt( 'oldid' ); + $oldid = $oldid ? $oldid : $wgRequest->getInt( 'diff' ); + // Allow oldid to override a changed or missing title + if( $oldid ) { + $rev = Revision::newFromId( $oldid ); + $ret = $rev ? $rev->getTitle() : $ret; } } return $ret; @@ -447,8 +450,10 @@ class MediaWiki { $article->view(); break; case 'raw': // includes JS/CSS + wfProfileIn( __METHOD__.'-raw' ); $raw = new RawPage( $article ); $raw->view(); + wfProfileOut( __METHOD__.'-raw' ); break; case 'watch': case 'unwatch': diff --git a/includes/WikiError.php b/includes/WikiError.php index 41edb2f3..251c1742 100644 --- a/includes/WikiError.php +++ b/includes/WikiError.php @@ -75,6 +75,16 @@ class WikiErrorMsg extends WikiError { $args = func_get_args(); array_shift( $args ); $this->mMessage = wfMsgReal( $message, $args, true ); + $this->mMsgKey = $message; + $this->mMsgArgs = $args; + } + + function getMessageKey() { + return $this->mMsgKey; + } + + function getMessageArgs() { + return $this->mMsgArgs; } } diff --git a/includes/Xml.php b/includes/Xml.php index 68990d86..bbe0717c 100644 --- a/includes/Xml.php +++ b/includes/Xml.php @@ -172,6 +172,36 @@ class Xml { . implode( "\n", $options ) . self::closeElement( 'select' ); } + + /** + * @param $year Integer + * @param $month Integer + * @return string Formatted HTML + */ + public static function dateMenu( $year, $month ) { + # Offset overrides year/month selection + if( $month && $month !== -1 ) { + $encMonth = intval( $month ); + } else { + $encMonth = ''; + } + if( $year ) { + $encYear = intval( $year ); + } else if( $encMonth ) { + $thisMonth = intval( gmdate( 'n' ) ); + $thisYear = intval( gmdate( 'Y' ) ); + if( intval($encMonth) > $thisMonth ) { + $thisYear--; + } + $encYear = $thisYear; + } else { + $encYear = ''; + } + return Xml::label( wfMsg( 'year' ), 'year' ) . ' '. + Xml::input( 'year', 4, $encYear, array('id' => 'year', 'maxlength' => 4) ) . ' '. + Xml::label( wfMsg( 'month' ), 'month' ) . ' '. + Xml::monthSelector( $encMonth, -1 ); + } /** * @@ -641,7 +671,6 @@ class Xml { foreach( $fields as $labelmsg => $input ) { $id = "mw-$labelmsg"; - $form .= Xml::openElement( 'tr', array( 'id' => $id ) ); $form .= Xml::tags( 'td', array('class' => 'mw-label'), wfMsgExt( $labelmsg, array('parseinline') ) ); $form .= Xml::openElement( 'td', array( 'class' => 'mw-input' ) ) . $input . Xml::closeElement( 'td' ); @@ -649,7 +678,7 @@ class Xml { } if( $submitLabel ) { - $form .= Xml::openElement( 'tr', array( 'id' => $id ) ); + $form .= Xml::openElement( 'tr' ); $form .= Xml::tags( 'td', array(), '' ); $form .= Xml::openElement( 'td', array( 'class' => 'mw-submit' ) ) . Xml::submitButton( wfMsg( $submitLabel ) ) . Xml::closeElement( 'td' ); $form .= Xml::closeElement( 'tr' ); diff --git a/includes/ZhConversion.php b/includes/ZhConversion.php index 4c1e0ae8..d7655df0 100644 --- a/includes/ZhConversion.php +++ b/includes/ZhConversion.php @@ -7,11356 +7,16956 @@ */ $zh2Hant = array( -"画"=>"畫", -"丰"=>"豐", -"帘"=>"簾", -"愿"=>"願", -"云"=>"雲", -"筑"=>"築", -"厂"=>"廠", -"广"=>"廣", -"别"=>"別", -"冲"=>"沖", -"种"=>"種", -"虫"=>"蟲", -"担"=>"擔", -"党"=>"黨", -"儿"=>"兒", -"柜"=>"櫃", -"坏"=>"壞", -"几"=>"幾", -"价"=>"價", -"据"=>"據", -"适"=>"適", -"蜡"=>"蠟", -"腊"=>"臘", -"万"=>"萬", -"宁"=>"寧", -"苹"=>"蘋", -"确"=>"確", -"胜"=>"勝", -"术"=>"術", -"体"=>"體", -"涂"=>"塗", -"叶"=>"葉", -"与"=>"與", -"恶"=>"惡", -"发"=>"發", -"复"=>"復", -"汇"=>"匯", -"获"=>"獲", -"饥"=>"飢", -"尽"=>"盡", -"历"=>"歷", -"卤"=>"鹵", -"弥"=>"彌", -"签"=>"簽", -"纤"=>"纖", -"苏"=>"蘇", -"坛"=>"壇", -"团"=>"團", -"须"=>"須", -"脏"=>"臟", -"钟"=>"鍾", -"药"=>"葯", -"当"=>"當", -"蕴"=>"蘊", -"线"=>"線", -"为"=>"為", -"产"=>"產", -"众"=>"眾", -"伪"=>"偽", -"凫"=>"鳧", -"厕"=>"廁", -"启"=>"啟", -"墙"=>"牆", -"壳"=>"殼", -"奖"=>"獎", -"妫"=>"媯", -"并"=>"並", -"录"=>"錄", -"悫"=>"愨", -"极"=>"極", -"沩"=>"溈", -"瘘"=>"瘺", -"硷"=>"礆", -"竖"=>"豎", -"绝"=>"絕", -"绣"=>"綉", -"绦"=>"絛", -"绱"=>"緔", -"绷"=>"綳", -"绿"=>"綠", -"缰"=>"韁", -"苧"=>"苎", -"莼"=>"蒓", -"说"=>"說", -"谣"=>"謠", -"谫"=>"譾", -"赃"=>"贓", -"赍"=>"齎", -"赝"=>"贗", -"酝"=>"醞", -"钩"=>"鉤", -"钵"=>"缽", -"锈"=>"銹", -"锐"=>"銳", -"锨"=>"杴", -"镌"=>"鐫", -"镢"=>"钁", -"阅"=>"閱", -"颓"=>"頹", -"颜"=>"顏", -"骂"=>"罵", -"鲇"=>"鯰", -"鲞"=>"鯗", -"鳄"=>"鱷", -"鸡"=>"雞", -"鹚"=>"鶿", -"仑"=>"侖", -"赞"=>"贊", -"荡"=>"盪", -"锤"=>"錘", -"㟆"=>"㠏", -"㛟"=>"𡞵", -"专"=>"專", -"业"=>"業", -"丛"=>"叢", -"东"=>"東", -"丝"=>"絲", -"丢"=>"丟", -"两"=>"兩", -"严"=>"嚴", -"丧"=>"喪", -"个"=>"個", -"临"=>"臨", -"丽"=>"麗", -"举"=>"舉", -"义"=>"義", -"乌"=>"烏", -"乐"=>"樂", -"乔"=>"喬", -"习"=>"習", -"乡"=>"鄉", -"书"=>"書", -"买"=>"買", -"乱"=>"亂", -"争"=>"爭", -"亏"=>"虧", -"亚"=>"亞", -"亩"=>"畝", -"亲"=>"親", -"亵"=>"褻", -"亸"=>"嚲", -"亿"=>"億", -"仅"=>"僅", -"从"=>"從", -"仓"=>"倉", -"仪"=>"儀", -"们"=>"們", -"优"=>"優", -"会"=>"會", -"伛"=>"傴", -"伞"=>"傘", -"伟"=>"偉", -"传"=>"傳", -"伣"=>"俔", -"伤"=>"傷", -"伥"=>"倀", -"伦"=>"倫", -"伧"=>"傖", -"伫"=>"佇", -"佥"=>"僉", -"侠"=>"俠", -"侣"=>"侶", -"侥"=>"僥", -"侦"=>"偵", -"侧"=>"側", -"侨"=>"僑", -"侩"=>"儈", -"侪"=>"儕", -"侬"=>"儂", -"俣"=>"俁", -"俦"=>"儔", -"俨"=>"儼", -"俩"=>"倆", -"俪"=>"儷", -"俫"=>"倈", -"俭"=>"儉", -"债"=>"債", -"倾"=>"傾", -"偬"=>"傯", -"偻"=>"僂", -"偾"=>"僨", -"偿"=>"償", -"傥"=>"儻", -"傧"=>"儐", -"储"=>"儲", -"傩"=>"儺", -"㑩"=>"儸", -"兑"=>"兌", -"兖"=>"兗", -"兰"=>"蘭", -"关"=>"關", -"兴"=>"興", -"兹"=>"茲", -"养"=>"養", -"兽"=>"獸", -"冁"=>"囅", -"内"=>"內", -"冈"=>"岡", -"册"=>"冊", -"写"=>"寫", -"军"=>"軍", -"农"=>"農", -"冯"=>"馮", -"决"=>"決", -"况"=>"況", -"冻"=>"凍", -"净"=>"凈", -"凉"=>"涼", -"减"=>"減", -"凑"=>"湊", -"凛"=>"凜", -"凤"=>"鳳", -"凭"=>"憑", -"凯"=>"凱", -"击"=>"擊", -"凿"=>"鑿", -"刍"=>"芻", -"刘"=>"劉", -"则"=>"則", -"刚"=>"剛", -"创"=>"創", -"删"=>"刪", -"刬"=>"剗", -"刭"=>"剄", -"刹"=>"剎", -"刽"=>"劊", -"刿"=>"劌", -"剀"=>"剴", -"剂"=>"劑", -"剐"=>"剮", -"剑"=>"劍", -"剥"=>"剝", -"剧"=>"劇", -"㓥"=>"劏", -"㔉"=>"劚", -"劝"=>"勸", -"办"=>"辦", -"务"=>"務", -"劢"=>"勱", -"动"=>"動", -"励"=>"勵", -"劲"=>"勁", -"劳"=>"勞", -"势"=>"勢", -"勋"=>"勛", -"勚"=>"勩", -"匀"=>"勻", -"匦"=>"匭", -"匮"=>"匱", -"区"=>"區", -"医"=>"醫", -"华"=>"華", -"协"=>"協", -"单"=>"單", -"卖"=>"賣", -"卢"=>"盧", -"卫"=>"衛", -"却"=>"卻", -"厅"=>"廳", -"厉"=>"厲", -"压"=>"壓", -"厌"=>"厭", -"厍"=>"厙", -"厐"=>"龎", -"厢"=>"廂", -"厣"=>"厴", -"厦"=>"廈", -"厨"=>"廚", -"厩"=>"廄", -"厮"=>"廝", -"县"=>"縣", -"叁"=>"叄", -"参"=>"參", -"双"=>"雙", -"变"=>"變", -"叙"=>"敘", -"叠"=>"疊", -"号"=>"號", -"叹"=>"嘆", -"叽"=>"嘰", -"吓"=>"嚇", -"吕"=>"呂", -"吗"=>"嗎", -"吣"=>"唚", -"吨"=>"噸", -"听"=>"聽", -"吴"=>"吳", -"呐"=>"吶", -"呒"=>"嘸", -"呓"=>"囈", -"呕"=>"嘔", -"呖"=>"嚦", -"呗"=>"唄", -"员"=>"員", -"呙"=>"咼", -"呛"=>"嗆", -"呜"=>"嗚", -"咏"=>"詠", -"咙"=>"嚨", -"咛"=>"嚀", -"咝"=>"噝", -"咤"=>"吒", -"响"=>"響", -"哑"=>"啞", -"哒"=>"噠", -"哓"=>"嘵", -"哔"=>"嗶", -"哕"=>"噦", -"哗"=>"嘩", -"哙"=>"噲", -"哜"=>"嚌", -"哝"=>"噥", -"哟"=>"喲", -"唛"=>"嘜", -"唝"=>"嗊", -"唠"=>"嘮", -"唡"=>"啢", -"唢"=>"嗩", -"唤"=>"喚", -"啧"=>"嘖", -"啬"=>"嗇", -"啭"=>"囀", -"啮"=>"嚙", -"啴"=>"嘽", -"啸"=>"嘯", -"㖞"=>"喎", -"喷"=>"噴", -"喽"=>"嘍", -"喾"=>"嚳", -"嗫"=>"囁", -"嗳"=>"噯", -"嘘"=>"噓", -"嘤"=>"嚶", -"嘱"=>"囑", -"㖊"=>"噚", -"噜"=>"嚕", -"嚣"=>"囂", -"园"=>"園", -"囱"=>"囪", -"围"=>"圍", -"囵"=>"圇", -"国"=>"國", -"图"=>"圖", -"圆"=>"圓", -"圣"=>"聖", -"圹"=>"壙", -"场"=>"場", -"块"=>"塊", -"坚"=>"堅", -"坜"=>"壢", -"坝"=>"壩", -"坞"=>"塢", -"坟"=>"墳", -"坠"=>"墜", -"垄"=>"壟", -"垅"=>"壠", -"垆"=>"壚", -"垒"=>"壘", -"垦"=>"墾", -"垩"=>"堊", -"垫"=>"墊", -"垭"=>"埡", -"垱"=>"壋", -"垲"=>"塏", -"垴"=>"堖", -"埘"=>"塒", -"埙"=>"塤", -"埚"=>"堝", -"埯"=>"垵", -"堑"=>"塹", -"堕"=>"墮", -"𡒄"=>"壈", -"壮"=>"壯", -"声"=>"聲", -"壶"=>"壺", -"壸"=>"壼", -"处"=>"處", -"备"=>"備", -"够"=>"夠", -"头"=>"頭", -"夸"=>"誇", -"夹"=>"夾", -"夺"=>"奪", -"奁"=>"奩", -"奂"=>"奐", -"奋"=>"奮", -"奥"=>"奧", -"妆"=>"妝", -"妇"=>"婦", -"妈"=>"媽", -"妩"=>"嫵", -"妪"=>"嫗", -"姗"=>"姍", -"姹"=>"奼", -"娄"=>"婁", -"娅"=>"婭", -"娆"=>"嬈", -"娇"=>"嬌", -"娈"=>"孌", -"娱"=>"娛", -"娲"=>"媧", -"娴"=>"嫻", -"婳"=>"嫿", -"婴"=>"嬰", -"婵"=>"嬋", -"婶"=>"嬸", -"媪"=>"媼", -"嫒"=>"嬡", -"嫔"=>"嬪", -"嫱"=>"嬙", -"嬷"=>"嬤", -"孙"=>"孫", -"学"=>"學", -"孪"=>"孿", -"宝"=>"寶", -"实"=>"實", -"宠"=>"寵", -"审"=>"審", -"宪"=>"憲", -"宫"=>"宮", -"宽"=>"寬", -"宾"=>"賓", -"寝"=>"寢", -"对"=>"對", -"寻"=>"尋", -"导"=>"導", -"寿"=>"壽", -"将"=>"將", -"尔"=>"爾", -"尘"=>"塵", -"尝"=>"嘗", -"尧"=>"堯", -"尴"=>"尷", -"尸"=>"屍", -"层"=>"層", -"屃"=>"屓", -"屉"=>"屜", -"届"=>"屆", -"属"=>"屬", -"屡"=>"屢", -"屦"=>"屨", -"屿"=>"嶼", -"岁"=>"歲", -"岂"=>"豈", -"岖"=>"嶇", -"岗"=>"崗", -"岘"=>"峴", -"岙"=>"嶴", -"岚"=>"嵐", -"岛"=>"島", -"岭"=>"嶺", -"岽"=>"崬", -"岿"=>"巋", -"峄"=>"嶧", -"峡"=>"峽", -"峣"=>"嶢", -"峤"=>"嶠", -"峥"=>"崢", -"峦"=>"巒", -"崂"=>"嶗", -"崃"=>"崍", -"崄"=>"嶮", -"崭"=>"嶄", -"嵘"=>"嶸", -"嵚"=>"嶔", -"嵝"=>"嶁", -"巅"=>"巔", -"巩"=>"鞏", -"巯"=>"巰", -"币"=>"幣", -"帅"=>"帥", -"师"=>"師", -"帏"=>"幃", -"帐"=>"帳", -"帜"=>"幟", -"带"=>"帶", -"帧"=>"幀", -"帮"=>"幫", -"帱"=>"幬", -"帻"=>"幘", -"帼"=>"幗", -"幂"=>"冪", -"庆"=>"慶", -"庐"=>"廬", -"庑"=>"廡", -"库"=>"庫", -"应"=>"應", -"庙"=>"廟", -"庞"=>"龐", -"废"=>"廢", -"廪"=>"廩", -"开"=>"開", -"异"=>"異", -"弃"=>"棄", -"弑"=>"弒", -"张"=>"張", -"弪"=>"弳", -"弯"=>"彎", -"弹"=>"彈", -"强"=>"強", -"归"=>"歸", -"彦"=>"彥", -"彻"=>"徹", -"径"=>"徑", -"徕"=>"徠", -"忆"=>"憶", -"忏"=>"懺", -"忧"=>"憂", -"忾"=>"愾", -"怀"=>"懷", -"态"=>"態", -"怂"=>"慫", -"怃"=>"憮", -"怄"=>"慪", -"怅"=>"悵", -"怆"=>"愴", -"怜"=>"憐", -"总"=>"總", -"怼"=>"懟", -"怿"=>"懌", -"恋"=>"戀", -"恒"=>"恆", -"恳"=>"懇", -"恸"=>"慟", -"恹"=>"懨", -"恺"=>"愷", -"恻"=>"惻", -"恼"=>"惱", -"恽"=>"惲", -"悦"=>"悅", -"悬"=>"懸", -"悭"=>"慳", -"悮"=>"悞", -"悯"=>"憫", -"惊"=>"驚", -"惧"=>"懼", -"惨"=>"慘", -"惩"=>"懲", -"惫"=>"憊", -"惬"=>"愜", -"惭"=>"慚", -"惮"=>"憚", -"惯"=>"慣", -"愠"=>"慍", -"愤"=>"憤", -"愦"=>"憒", -"慑"=>"懾", -"懑"=>"懣", -"懒"=>"懶", -"懔"=>"懍", -"戆"=>"戇", -"戋"=>"戔", -"戏"=>"戲", -"戗"=>"戧", -"战"=>"戰", -"戬"=>"戩", -"戯"=>"戱", -"户"=>"戶", -"扑"=>"撲", -"执"=>"執", -"扩"=>"擴", -"扪"=>"捫", -"扫"=>"掃", -"扬"=>"揚", -"扰"=>"擾", -"抚"=>"撫", -"抛"=>"拋", -"抟"=>"摶", -"抠"=>"摳", -"抡"=>"掄", -"抢"=>"搶", -"护"=>"護", -"报"=>"報", -"拟"=>"擬", -"拢"=>"攏", -"拣"=>"揀", -"拥"=>"擁", -"拦"=>"攔", -"拧"=>"擰", -"拨"=>"撥", -"择"=>"擇", -"挂"=>"掛", -"挚"=>"摯", -"挛"=>"攣", -"挜"=>"掗", -"挝"=>"撾", -"挞"=>"撻", -"挟"=>"挾", -"挠"=>"撓", -"挡"=>"擋", -"挢"=>"撟", -"挣"=>"掙", -"挤"=>"擠", -"挥"=>"揮", -"挦"=>"撏", -"捝"=>"挩", -"捞"=>"撈", -"损"=>"損", -"捡"=>"撿", -"换"=>"換", -"捣"=>"搗", -"掳"=>"擄", -"掴"=>"摑", -"掷"=>"擲", -"掸"=>"撣", -"掺"=>"摻", -"掼"=>"摜", -"揽"=>"攬", -"揾"=>"搵", -"揿"=>"撳", -"搀"=>"攙", -"搁"=>"擱", -"搂"=>"摟", -"搅"=>"攪", -"携"=>"攜", -"摄"=>"攝", -"摅"=>"攄", -"摆"=>"擺", -"摇"=>"搖", -"摈"=>"擯", -"摊"=>"攤", -"撄"=>"攖", -"撑"=>"撐", -"㧑"=>"撝", -"撵"=>"攆", -"撷"=>"擷", -"撸"=>"擼", -"撺"=>"攛", -"㧟"=>"擓", -"擞"=>"擻", -"攒"=>"攢", -"敌"=>"敵", -"敛"=>"斂", -"数"=>"數", -"斋"=>"齋", -"斓"=>"斕", -"斩"=>"斬", -"断"=>"斷", -"无"=>"無", -"旧"=>"舊", -"时"=>"時", -"旷"=>"曠", -"旸"=>"暘", -"昙"=>"曇", -"昼"=>"晝", -"昽"=>"曨", -"显"=>"顯", -"晋"=>"晉", -"晒"=>"曬", -"晓"=>"曉", -"晔"=>"曄", -"晕"=>"暈", -"晖"=>"暉", -"暂"=>"暫", -"暧"=>"曖", -"机"=>"機", -"杀"=>"殺", -"杂"=>"雜", -"权"=>"權", -"杆"=>"桿", -"条"=>"條", -"来"=>"來", -"杨"=>"楊", -"杩"=>"榪", -"杰"=>"傑", -"构"=>"構", -"枞"=>"樅", -"枢"=>"樞", -"枣"=>"棗", -"枥"=>"櫪", -"枧"=>"梘", -"枨"=>"棖", -"枪"=>"槍", -"枫"=>"楓", -"枭"=>"梟", -"柠"=>"檸", -"柽"=>"檉", -"栀"=>"梔", -"栅"=>"柵", -"标"=>"標", -"栈"=>"棧", -"栉"=>"櫛", -"栊"=>"櫳", -"栋"=>"棟", -"栌"=>"櫨", -"栎"=>"櫟", -"栏"=>"欄", -"树"=>"樹", -"栖"=>"棲", -"样"=>"樣", -"栾"=>"欒", -"桠"=>"椏", -"桡"=>"橈", -"桢"=>"楨", -"档"=>"檔", -"桤"=>"榿", -"桥"=>"橋", -"桦"=>"樺", -"桧"=>"檜", -"桨"=>"槳", -"桩"=>"樁", -"梦"=>"夢", -"梼"=>"檮", -"梾"=>"棶", -"梿"=>"槤", -"检"=>"檢", -"棁"=>"梲", -"棂"=>"欞", -"椁"=>"槨", -"椟"=>"櫝", -"椠"=>"槧", -"椤"=>"欏", -"椭"=>"橢", -"楼"=>"樓", -"榄"=>"欖", -"榅"=>"榲", -"榇"=>"櫬", -"榈"=>"櫚", -"榉"=>"櫸", -"槚"=>"檟", -"槛"=>"檻", -"槟"=>"檳", -"槠"=>"櫧", -"横"=>"橫", -"樯"=>"檣", -"樱"=>"櫻", -"橥"=>"櫫", -"橱"=>"櫥", -"橹"=>"櫓", -"橼"=>"櫞", -"檩"=>"檁", -"欢"=>"歡", -"欤"=>"歟", -"欧"=>"歐", -"歼"=>"殲", -"殁"=>"歿", -"殇"=>"殤", -"残"=>"殘", -"殒"=>"殞", -"殓"=>"殮", -"殚"=>"殫", -"殡"=>"殯", -"㱮"=>"殨", -"㱩"=>"殰", -"殴"=>"毆", -"毁"=>"毀", -"毂"=>"轂", -"毕"=>"畢", -"毙"=>"斃", -"毡"=>"氈", -"毵"=>"毿", -"氇"=>"氌", -"气"=>"氣", -"氢"=>"氫", -"氩"=>"氬", -"氲"=>"氳", -"汉"=>"漢", -"汤"=>"湯", -"汹"=>"洶", -"沟"=>"溝", -"没"=>"沒", -"沣"=>"灃", -"沤"=>"漚", -"沥"=>"瀝", -"沦"=>"淪", -"沧"=>"滄", -"沪"=>"滬", -"泞"=>"濘", -"泪"=>"淚", -"泶"=>"澩", -"泷"=>"瀧", -"泸"=>"瀘", -"泺"=>"濼", -"泻"=>"瀉", -"泼"=>"潑", -"泽"=>"澤", -"泾"=>"涇", -"洁"=>"潔", -"洒"=>"灑", -"洼"=>"窪", -"浃"=>"浹", -"浅"=>"淺", -"浆"=>"漿", -"浇"=>"澆", -"浈"=>"湞", -"浊"=>"濁", -"测"=>"測", -"浍"=>"澮", -"济"=>"濟", -"浏"=>"瀏", -"浐"=>"滻", -"浑"=>"渾", -"浒"=>"滸", -"浓"=>"濃", -"浔"=>"潯", -"涛"=>"濤", -"涝"=>"澇", -"涞"=>"淶", -"涟"=>"漣", -"涠"=>"潿", -"涡"=>"渦", -"涣"=>"渙", -"涤"=>"滌", -"润"=>"潤", -"涧"=>"澗", -"涨"=>"漲", -"涩"=>"澀", -"渊"=>"淵", -"渌"=>"淥", -"渍"=>"漬", -"渎"=>"瀆", -"渐"=>"漸", -"渑"=>"澠", -"渔"=>"漁", -"渖"=>"瀋", -"渗"=>"滲", -"温"=>"溫", -"湾"=>"灣", -"湿"=>"濕", -"溃"=>"潰", -"溅"=>"濺", -"溆"=>"漵", -"滗"=>"潷", -"滚"=>"滾", -"滞"=>"滯", -"滟"=>"灧", -"滠"=>"灄", -"满"=>"滿", -"滢"=>"瀅", -"滤"=>"濾", -"滥"=>"濫", -"滦"=>"灤", -"滨"=>"濱", -"滩"=>"灘", -"滪"=>"澦", -"漤"=>"灠", -"潆"=>"瀠", -"潇"=>"瀟", -"潋"=>"瀲", -"潍"=>"濰", -"潜"=>"潛", -"潴"=>"瀦", -"澜"=>"瀾", -"濑"=>"瀨", -"濒"=>"瀕", -"㲿"=>"瀇", -"灏"=>"灝", -"灭"=>"滅", -"灯"=>"燈", -"灵"=>"靈", -"灾"=>"災", -"灿"=>"燦", -"炀"=>"煬", -"炉"=>"爐", -"炖"=>"燉", -"炜"=>"煒", -"炝"=>"熗", -"点"=>"點", -"炼"=>"煉", -"炽"=>"熾", -"烁"=>"爍", -"烂"=>"爛", -"烃"=>"烴", -"烛"=>"燭", -"烟"=>"煙", -"烦"=>"煩", -"烧"=>"燒", -"烨"=>"燁", -"烩"=>"燴", -"烫"=>"燙", -"烬"=>"燼", -"热"=>"熱", -"焕"=>"煥", -"焖"=>"燜", -"焘"=>"燾", -"㶽"=>"煱", -"煴"=>"熅", -"㶶"=>"燶", -"爱"=>"愛", -"爷"=>"爺", -"牍"=>"牘", -"牦"=>"氂", -"牵"=>"牽", -"牺"=>"犧", -"犊"=>"犢", -"状"=>"狀", -"犷"=>"獷", -"犸"=>"獁", -"犹"=>"猶", -"狈"=>"狽", -"狝"=>"獮", -"狞"=>"獰", -"独"=>"獨", -"狭"=>"狹", -"狮"=>"獅", -"狯"=>"獪", -"狰"=>"猙", -"狱"=>"獄", -"狲"=>"猻", -"猃"=>"獫", -"猎"=>"獵", -"猕"=>"獼", -"猡"=>"玀", -"猪"=>"豬", -"猫"=>"貓", -"猬"=>"蝟", -"献"=>"獻", -"獭"=>"獺", -"㺍"=>"獱", -"玑"=>"璣", -"玚"=>"瑒", -"玛"=>"瑪", -"玮"=>"瑋", -"环"=>"環", -"现"=>"現", -"玱"=>"瑲", -"玺"=>"璽", -"珐"=>"琺", -"珑"=>"瓏", -"珰"=>"璫", -"珲"=>"琿", -"琏"=>"璉", -"琐"=>"瑣", -"琼"=>"瓊", -"瑶"=>"瑤", -"瑷"=>"璦", -"璎"=>"瓔", -"瓒"=>"瓚", -"瓯"=>"甌", -"电"=>"電", -"画"=>"畫", -"畅"=>"暢", -"畴"=>"疇", -"疖"=>"癤", -"疗"=>"療", -"疟"=>"瘧", -"疠"=>"癘", -"疡"=>"瘍", -"疬"=>"癧", -"疭"=>"瘲", -"疮"=>"瘡", -"疯"=>"瘋", -"疱"=>"皰", -"疴"=>"痾", -"痈"=>"癰", -"痉"=>"痙", -"痒"=>"癢", -"痖"=>"瘂", -"痨"=>"癆", -"痪"=>"瘓", -"痫"=>"癇", -"瘅"=>"癉", -"瘆"=>"瘮", -"瘗"=>"瘞", -"瘪"=>"癟", -"瘫"=>"癱", -"瘾"=>"癮", -"瘿"=>"癭", -"癞"=>"癩", -"癣"=>"癬", -"癫"=>"癲", -"皑"=>"皚", -"皱"=>"皺", -"皲"=>"皸", -"盏"=>"盞", -"盐"=>"鹽", -"监"=>"監", -"盖"=>"蓋", -"盗"=>"盜", -"盘"=>"盤", -"眍"=>"瞘", -"眦"=>"眥", -"眬"=>"矓", -"睁"=>"睜", -"睐"=>"睞", -"睑"=>"瞼", -"瞆"=>"瞶", -"瞒"=>"瞞", -"䁖"=>"瞜", -"瞩"=>"矚", -"矫"=>"矯", -"矶"=>"磯", -"矾"=>"礬", -"矿"=>"礦", -"砀"=>"碭", -"码"=>"碼", -"砖"=>"磚", -"砗"=>"硨", -"砚"=>"硯", -"砜"=>"碸", -"砺"=>"礪", -"砻"=>"礱", -"砾"=>"礫", -"础"=>"礎", -"硁"=>"硜", -"硕"=>"碩", -"硖"=>"硤", -"硗"=>"磽", -"硙"=>"磑", -"碍"=>"礙", -"碛"=>"磧", -"碜"=>"磣", -"碱"=>"鹼", -"礼"=>"禮", -"祃"=>"禡", -"祎"=>"禕", -"祢"=>"禰", -"祯"=>"禎", -"祷"=>"禱", -"祸"=>"禍", -"禀"=>"稟", -"禄"=>"祿", -"禅"=>"禪", -"离"=>"離", -"秃"=>"禿", -"秆"=>"稈", -"积"=>"積", -"称"=>"稱", -"秽"=>"穢", -"秾"=>"穠", -"稆"=>"穭", -"税"=>"稅", -"䅉"=>"稏", -"稣"=>"穌", -"稳"=>"穩", -"穑"=>"穡", -"穷"=>"窮", -"窃"=>"竊", -"窍"=>"竅", -"窎"=>"窵", -"窑"=>"窯", -"窜"=>"竄", -"窝"=>"窩", -"窥"=>"窺", -"窦"=>"竇", -"窭"=>"窶", -"竞"=>"競", -"笃"=>"篤", -"笋"=>"筍", -"笔"=>"筆", -"笕"=>"筧", -"笺"=>"箋", -"笼"=>"籠", -"笾"=>"籩", -"筚"=>"篳", -"筛"=>"篩", -"筜"=>"簹", -"筝"=>"箏", -"䇲"=>"筴", -"筹"=>"籌", -"筼"=>"篔", -"简"=>"簡", -"箓"=>"籙", -"箦"=>"簀", -"箧"=>"篋", -"箨"=>"籜", -"箩"=>"籮", -"箪"=>"簞", -"箫"=>"簫", -"篑"=>"簣", -"篓"=>"簍", -"篮"=>"籃", -"篱"=>"籬", -"簖"=>"籪", -"籁"=>"籟", -"籴"=>"糴", -"类"=>"類", -"籼"=>"秈", -"粜"=>"糶", -"粝"=>"糲", -"粤"=>"粵", -"粪"=>"糞", -"粮"=>"糧", -"糁"=>"糝", -"糇"=>"餱", -"紧"=>"緊", -"䌷"=>"紬", -"䌹"=>"絅", -"絷"=>"縶", -"䌼"=>"綐", -"䌽"=>"綵", -"䌸"=>"縳", -"䍁"=>"繸", -"䍀"=>"繿", -"纟"=>"糹", -"纠"=>"糾", -"纡"=>"紆", -"红"=>"紅", -"纣"=>"紂", -"纥"=>"紇", -"约"=>"約", -"级"=>"級", -"纨"=>"紈", -"纩"=>"纊", -"纪"=>"紀", -"纫"=>"紉", -"纬"=>"緯", -"纭"=>"紜", -"纮"=>"紘", -"纯"=>"純", -"纰"=>"紕", -"纱"=>"紗", -"纲"=>"綱", -"纳"=>"納", -"纴"=>"紝", -"纵"=>"縱", -"纶"=>"綸", -"纷"=>"紛", -"纸"=>"紙", -"纹"=>"紋", -"纺"=>"紡", -"纻"=>"紵", -"纼"=>"紖", -"纽"=>"紐", -"纾"=>"紓", -"绀"=>"紺", -"绁"=>"紲", -"绂"=>"紱", -"练"=>"練", -"组"=>"組", -"绅"=>"紳", -"细"=>"細", -"织"=>"織", -"终"=>"終", -"绉"=>"縐", -"绊"=>"絆", -"绋"=>"紼", -"绌"=>"絀", -"绍"=>"紹", -"绎"=>"繹", -"经"=>"經", -"绐"=>"紿", -"绑"=>"綁", -"绒"=>"絨", -"结"=>"結", -"绔"=>"絝", -"绕"=>"繞", -"绖"=>"絰", -"绗"=>"絎", -"绘"=>"繪", -"给"=>"給", -"绚"=>"絢", -"绛"=>"絳", -"络"=>"絡", -"绞"=>"絞", -"统"=>"統", -"绠"=>"綆", -"绡"=>"綃", -"绢"=>"絹", -"绤"=>"綌", -"绥"=>"綏", -"继"=>"繼", -"绨"=>"綈", -"绩"=>"績", -"绪"=>"緒", -"绫"=>"綾", -"绬"=>"緓", -"续"=>"續", -"绮"=>"綺", -"绯"=>"緋", -"绰"=>"綽", -"绲"=>"緄", -"绳"=>"繩", -"维"=>"維", -"绵"=>"綿", -"绶"=>"綬", -"绸"=>"綢", -"绹"=>"綯", -"绺"=>"綹", -"绻"=>"綣", -"综"=>"綜", -"绽"=>"綻", -"绾"=>"綰", -"缀"=>"綴", -"缁"=>"緇", -"缂"=>"緙", -"缃"=>"緗", -"缄"=>"緘", -"缅"=>"緬", -"缆"=>"纜", -"缇"=>"緹", -"缈"=>"緲", -"缉"=>"緝", -"缊"=>"縕", -"缋"=>"繢", -"缌"=>"緦", -"缍"=>"綞", -"缎"=>"緞", -"缏"=>"緶", -"缑"=>"緱", -"缒"=>"縋", -"缓"=>"緩", -"缔"=>"締", -"缕"=>"縷", -"编"=>"編", -"缗"=>"緡", -"缘"=>"緣", -"缙"=>"縉", -"缚"=>"縛", -"缛"=>"縟", -"缜"=>"縝", -"缝"=>"縫", -"缞"=>"縗", -"缟"=>"縞", -"缠"=>"纏", -"缡"=>"縭", -"缢"=>"縊", -"缣"=>"縑", -"缤"=>"繽", -"缥"=>"縹", -"缦"=>"縵", -"缧"=>"縲", -"缨"=>"纓", -"缩"=>"縮", -"缪"=>"繆", -"缫"=>"繅", -"缬"=>"纈", -"缭"=>"繚", -"缮"=>"繕", -"缯"=>"繒", -"缱"=>"繾", -"缲"=>"繰", -"缳"=>"繯", -"缴"=>"繳", -"缵"=>"纘", -"罂"=>"罌", -"网"=>"網", -"罗"=>"羅", -"罚"=>"罰", -"罢"=>"罷", -"罴"=>"羆", -"羁"=>"羈", -"羟"=>"羥", -"翘"=>"翹", -"耢"=>"耮", -"耧"=>"耬", -"耸"=>"聳", -"耻"=>"恥", -"聂"=>"聶", -"聋"=>"聾", -"职"=>"職", -"聍"=>"聹", -"联"=>"聯", -"聩"=>"聵", -"聪"=>"聰", -"肃"=>"肅", -"肠"=>"腸", -"肤"=>"膚", -"肮"=>"骯", -"肴"=>"餚", -"肾"=>"腎", -"肿"=>"腫", -"胀"=>"脹", -"胁"=>"脅", -"胆"=>"膽", -"胧"=>"朧", -"胨"=>"腖", -"胪"=>"臚", -"胫"=>"脛", -"胶"=>"膠", -"脉"=>"脈", -"脍"=>"膾", -"脐"=>"臍", -"脑"=>"腦", -"脓"=>"膿", -"脔"=>"臠", -"脚"=>"腳", -"脱"=>"脫", -"脶"=>"腡", -"脸"=>"臉", -"腭"=>"齶", -"腻"=>"膩", -"腼"=>"靦", -"腽"=>"膃", -"腾"=>"騰", -"膑"=>"臏", -"臜"=>"臢", -"舆"=>"輿", -"舣"=>"艤", -"舰"=>"艦", -"舱"=>"艙", -"舻"=>"艫", -"艰"=>"艱", -"艳"=>"艷", -"艺"=>"藝", -"节"=>"節", -"芈"=>"羋", -"芗"=>"薌", -"芜"=>"蕪", -"芦"=>"蘆", -"苁"=>"蓯", -"苇"=>"葦", -"苈"=>"藶", -"苋"=>"莧", -"苌"=>"萇", -"苍"=>"蒼", -"苎"=>"苧", -"茎"=>"莖", -"茏"=>"蘢", -"茑"=>"蔦", -"茔"=>"塋", -"茕"=>"煢", -"茧"=>"繭", -"荆"=>"荊", -"荐"=>"薦", -"荙"=>"薘", -"荚"=>"莢", -"荛"=>"蕘", -"荜"=>"蓽", -"荞"=>"蕎", -"荟"=>"薈", -"荠"=>"薺", -"荣"=>"榮", -"荤"=>"葷", -"荥"=>"滎", -"荦"=>"犖", -"荧"=>"熒", -"荨"=>"蕁", -"荩"=>"藎", -"荪"=>"蓀", -"荫"=>"蔭", -"荬"=>"蕒", -"荭"=>"葒", -"荮"=>"葤", -"莅"=>"蒞", -"莱"=>"萊", -"莲"=>"蓮", -"莳"=>"蒔", -"莴"=>"萵", -"莶"=>"薟", -"莸"=>"蕕", -"莹"=>"瑩", -"莺"=>"鶯", -"萝"=>"蘿", -"萤"=>"螢", -"营"=>"營", -"萦"=>"縈", -"萧"=>"蕭", -"萨"=>"薩", -"葱"=>"蔥", -"蒇"=>"蕆", -"蒉"=>"蕢", -"蒋"=>"蔣", -"蒌"=>"蔞", -"蓝"=>"藍", -"蓟"=>"薊", -"蓠"=>"蘺", -"蓣"=>"蕷", -"蓥"=>"鎣", -"蓦"=>"驀", -"蔂"=>"虆", -"蔷"=>"薔", -"蔹"=>"蘞", -"蔺"=>"藺", -"蔼"=>"藹", -"蕰"=>"薀", -"蕲"=>"蘄", -"薮"=>"藪", -"䓕"=>"薳", -"藓"=>"蘚", -"蘖"=>"櫱", -"虏"=>"虜", -"虑"=>"慮", -"虚"=>"虛", -"虬"=>"虯", -"虮"=>"蟣", -"虽"=>"雖", -"虾"=>"蝦", -"虿"=>"蠆", -"蚀"=>"蝕", -"蚁"=>"蟻", -"蚂"=>"螞", -"蚕"=>"蠶", -"蚬"=>"蜆", -"蛊"=>"蠱", -"蛎"=>"蠣", -"蛏"=>"蟶", -"蛮"=>"蠻", -"蛰"=>"蟄", -"蛱"=>"蛺", -"蛲"=>"蟯", -"蛳"=>"螄", -"蛴"=>"蠐", -"蜕"=>"蛻", -"蜗"=>"蝸", -"蝇"=>"蠅", -"蝈"=>"蟈", -"蝉"=>"蟬", -"蝼"=>"螻", -"蝾"=>"蠑", -"螀"=>"螿", -"螨"=>"蟎", -"䗖"=>"螮", -"蟏"=>"蠨", -"衅"=>"釁", -"衔"=>"銜", -"补"=>"補", -"衬"=>"襯", -"衮"=>"袞", -"袄"=>"襖", -"袅"=>"裊", -"袆"=>"褘", -"袜"=>"襪", -"袭"=>"襲", -"袯"=>"襏", -"装"=>"裝", -"裆"=>"襠", -"裈"=>"褌", -"裢"=>"褳", -"裣"=>"襝", -"裤"=>"褲", -"裥"=>"襇", -"褛"=>"褸", -"褴"=>"襤", -"䙓"=>"襬", -"见"=>"見", -"观"=>"觀", -"觃"=>"覎", -"规"=>"規", -"觅"=>"覓", -"视"=>"視", -"觇"=>"覘", -"览"=>"覽", -"觉"=>"覺", -"觊"=>"覬", -"觋"=>"覡", -"觌"=>"覿", -"觍"=>"覥", -"觎"=>"覦", -"觏"=>"覯", -"觐"=>"覲", -"觑"=>"覷", -"觞"=>"觴", -"触"=>"觸", -"觯"=>"觶", -"訚"=>"誾", -"䜣"=>"訢", -"誉"=>"譽", -"誊"=>"謄", -"䜧"=>"譅", -"讠"=>"訁", -"计"=>"計", -"订"=>"訂", -"讣"=>"訃", -"认"=>"認", -"讥"=>"譏", -"讦"=>"訐", -"讧"=>"訌", -"讨"=>"討", -"让"=>"讓", -"讪"=>"訕", -"讫"=>"訖", -"讬"=>"託", -"训"=>"訓", -"议"=>"議", -"讯"=>"訊", -"记"=>"記", -"讱"=>"訒", -"讲"=>"講", -"讳"=>"諱", -"讴"=>"謳", -"讵"=>"詎", -"讶"=>"訝", -"讷"=>"訥", -"许"=>"許", -"讹"=>"訛", -"论"=>"論", -"讻"=>"訩", -"讼"=>"訟", -"讽"=>"諷", -"设"=>"設", -"访"=>"訪", -"诀"=>"訣", -"证"=>"證", -"诂"=>"詁", -"诃"=>"訶", -"评"=>"評", -"诅"=>"詛", -"识"=>"識", -"诇"=>"詗", -"诈"=>"詐", -"诉"=>"訴", -"诊"=>"診", -"诋"=>"詆", -"诌"=>"謅", -"词"=>"詞", -"诎"=>"詘", -"诏"=>"詔", -"诐"=>"詖", -"译"=>"譯", -"诒"=>"詒", -"诓"=>"誆", -"诔"=>"誄", -"试"=>"試", -"诖"=>"詿", -"诗"=>"詩", -"诘"=>"詰", -"诙"=>"詼", -"诚"=>"誠", -"诛"=>"誅", -"诜"=>"詵", -"话"=>"話", -"诞"=>"誕", -"诟"=>"詬", -"诠"=>"詮", -"诡"=>"詭", -"询"=>"詢", -"诣"=>"詣", -"诤"=>"諍", -"该"=>"該", -"详"=>"詳", -"诧"=>"詫", -"诨"=>"諢", -"诩"=>"詡", -"诪"=>"譸", -"诫"=>"誡", -"诬"=>"誣", -"语"=>"語", -"诮"=>"誚", -"误"=>"誤", -"诰"=>"誥", -"诱"=>"誘", -"诲"=>"誨", -"诳"=>"誑", -"诵"=>"誦", -"诶"=>"誒", -"请"=>"請", -"诸"=>"諸", -"诹"=>"諏", -"诺"=>"諾", -"读"=>"讀", -"诼"=>"諑", -"诽"=>"誹", -"课"=>"課", -"诿"=>"諉", -"谀"=>"諛", -"谁"=>"誰", -"谂"=>"諗", -"调"=>"調", -"谄"=>"諂", -"谅"=>"諒", -"谆"=>"諄", -"谇"=>"誶", -"谈"=>"談", -"谊"=>"誼", -"谋"=>"謀", -"谌"=>"諶", -"谍"=>"諜", -"谎"=>"謊", -"谏"=>"諫", -"谐"=>"諧", -"谑"=>"謔", -"谒"=>"謁", -"谓"=>"謂", -"谔"=>"諤", -"谕"=>"諭", -"谖"=>"諼", -"谗"=>"讒", -"谘"=>"諮", -"谙"=>"諳", -"谚"=>"諺", -"谛"=>"諦", -"谜"=>"謎", -"谝"=>"諞", -"谞"=>"諝", -"谟"=>"謨", -"谠"=>"讜", -"谡"=>"謖", -"谢"=>"謝", -"谤"=>"謗", -"谥"=>"謚", -"谦"=>"謙", -"谧"=>"謐", -"谨"=>"謹", -"谩"=>"謾", -"谪"=>"謫", -"谬"=>"謬", -"谭"=>"譚", -"谮"=>"譖", -"谯"=>"譙", -"谰"=>"讕", -"谱"=>"譜", -"谲"=>"譎", -"谳"=>"讞", -"谴"=>"譴", -"谵"=>"譫", -"谶"=>"讖", -"豮"=>"豶", -"䝙"=>"貙", -"䞐"=>"賰", -"贝"=>"貝", -"贞"=>"貞", -"负"=>"負", -"贠"=>"貟", -"贡"=>"貢", -"财"=>"財", -"责"=>"責", -"贤"=>"賢", -"败"=>"敗", -"账"=>"賬", -"货"=>"貨", -"质"=>"質", -"贩"=>"販", -"贪"=>"貪", -"贫"=>"貧", -"贬"=>"貶", -"购"=>"購", -"贮"=>"貯", -"贯"=>"貫", -"贰"=>"貳", -"贱"=>"賤", -"贲"=>"賁", -"贳"=>"貰", -"贴"=>"貼", -"贵"=>"貴", -"贶"=>"貺", -"贷"=>"貸", -"贸"=>"貿", -"费"=>"費", -"贺"=>"賀", -"贻"=>"貽", -"贼"=>"賊", -"贽"=>"贄", -"贾"=>"賈", -"贿"=>"賄", -"赀"=>"貲", -"赁"=>"賃", -"赂"=>"賂", -"资"=>"資", -"赅"=>"賅", -"赆"=>"贐", -"赇"=>"賕", -"赈"=>"賑", -"赉"=>"賚", -"赊"=>"賒", -"赋"=>"賦", -"赌"=>"賭", -"赎"=>"贖", -"赏"=>"賞", -"赐"=>"賜", -"赑"=>"贔", -"赒"=>"賙", -"赓"=>"賡", -"赔"=>"賠", -"赕"=>"賧", -"赖"=>"賴", -"赗"=>"賵", -"赘"=>"贅", -"赙"=>"賻", -"赚"=>"賺", -"赛"=>"賽", -"赜"=>"賾", -"赟"=>"贇", -"赠"=>"贈", -"赡"=>"贍", -"赢"=>"贏", -"赣"=>"贛", -"赪"=>"赬", -"赵"=>"趙", -"赶"=>"趕", -"趋"=>"趨", -"趱"=>"趲", -"趸"=>"躉", -"跃"=>"躍", -"跄"=>"蹌", -"跞"=>"躒", -"践"=>"踐", -"跶"=>"躂", -"跷"=>"蹺", -"跸"=>"蹕", -"跹"=>"躚", -"跻"=>"躋", -"踊"=>"踴", -"踌"=>"躊", -"踪"=>"蹤", -"踬"=>"躓", -"踯"=>"躑", -"蹑"=>"躡", -"蹒"=>"蹣", -"蹰"=>"躕", -"蹿"=>"躥", -"躏"=>"躪", -"躜"=>"躦", -"躯"=>"軀", -"车"=>"車", -"轧"=>"軋", -"轨"=>"軌", -"轩"=>"軒", -"轪"=>"軑", -"轫"=>"軔", -"转"=>"轉", -"轭"=>"軛", -"轮"=>"輪", -"软"=>"軟", -"轰"=>"轟", -"轱"=>"軲", -"轲"=>"軻", -"轳"=>"轤", -"轴"=>"軸", -"轵"=>"軹", -"轶"=>"軼", -"轷"=>"軤", -"轸"=>"軫", -"轹"=>"轢", -"轺"=>"軺", -"轻"=>"輕", -"轼"=>"軾", -"载"=>"載", -"轾"=>"輊", -"轿"=>"轎", -"辀"=>"輈", -"辁"=>"輇", -"辂"=>"輅", -"较"=>"較", -"辄"=>"輒", -"辅"=>"輔", -"辆"=>"輛", -"辇"=>"輦", -"辈"=>"輩", -"辉"=>"輝", -"辊"=>"輥", -"辋"=>"輞", -"辌"=>"輬", -"辍"=>"輟", -"辎"=>"輜", -"辏"=>"輳", -"辐"=>"輻", -"辑"=>"輯", -"辒"=>"轀", -"输"=>"輸", -"辔"=>"轡", -"辕"=>"轅", -"辖"=>"轄", -"辗"=>"輾", -"辘"=>"轆", -"辙"=>"轍", -"辚"=>"轔", -"辞"=>"辭", -"辩"=>"辯", -"辫"=>"辮", -"边"=>"邊", -"辽"=>"遼", -"达"=>"達", -"迁"=>"遷", -"过"=>"過", -"迈"=>"邁", -"运"=>"運", -"还"=>"還", -"这"=>"這", -"进"=>"進", -"远"=>"遠", -"违"=>"違", -"连"=>"連", -"迟"=>"遲", -"迩"=>"邇", -"迳"=>"逕", -"迹"=>"跡", -"选"=>"選", -"逊"=>"遜", -"递"=>"遞", -"逦"=>"邐", -"逻"=>"邏", -"遗"=>"遺", -"遥"=>"遙", -"邓"=>"鄧", -"邝"=>"鄺", -"邬"=>"鄔", -"邮"=>"郵", -"邹"=>"鄒", -"邺"=>"鄴", -"邻"=>"鄰", -"郏"=>"郟", -"郐"=>"鄶", -"郑"=>"鄭", -"郓"=>"鄆", -"郦"=>"酈", -"郧"=>"鄖", -"郸"=>"鄲", -"酂"=>"酇", -"酦"=>"醱", -"酱"=>"醬", -"酽"=>"釅", -"酾"=>"釃", -"酿"=>"釀", -"释"=>"釋", -"鉴"=>"鑒", -"銮"=>"鑾", -"錾"=>"鏨", -"𨱏"=>"鎝", -"钅"=>"釒", -"钆"=>"釓", -"钇"=>"釔", -"针"=>"針", -"钉"=>"釘", -"钊"=>"釗", -"钋"=>"釙", -"钌"=>"釕", -"钍"=>"釷", -"钎"=>"釺", -"钏"=>"釧", -"钐"=>"釤", -"钑"=>"鈒", -"钒"=>"釩", -"钓"=>"釣", -"钔"=>"鍆", -"钕"=>"釹", -"钖"=>"鍚", -"钗"=>"釵", -"钘"=>"鈃", -"钙"=>"鈣", -"钚"=>"鈈", -"钛"=>"鈦", -"钜"=>"鉅", -"钝"=>"鈍", -"钞"=>"鈔", -"钠"=>"鈉", -"钡"=>"鋇", -"钢"=>"鋼", -"钣"=>"鈑", -"钤"=>"鈐", -"钥"=>"鑰", -"钦"=>"欽", -"钧"=>"鈞", -"钨"=>"鎢", -"钪"=>"鈧", -"钫"=>"鈁", -"钬"=>"鈥", -"钭"=>"鈄", -"钮"=>"鈕", -"钯"=>"鈀", -"钰"=>"鈺", -"钱"=>"錢", -"钲"=>"鉦", -"钳"=>"鉗", -"钴"=>"鈷", -"钶"=>"鈳", -"钷"=>"鉕", -"钸"=>"鈽", -"钹"=>"鈸", -"钺"=>"鉞", -"钻"=>"鑽", -"钼"=>"鉬", -"钽"=>"鉭", -"钾"=>"鉀", -"钿"=>"鈿", -"铀"=>"鈾", -"铁"=>"鐵", -"铂"=>"鉑", -"铃"=>"鈴", -"铄"=>"鑠", -"铅"=>"鉛", -"铆"=>"鉚", -"铇"=>"鉋", -"铈"=>"鈰", -"铉"=>"鉉", -"铊"=>"鉈", -"铋"=>"鉍", -"铌"=>"鈮", -"铍"=>"鈹", -"铎"=>"鐸", -"铏"=>"鉶", -"铐"=>"銬", -"铑"=>"銠", -"铒"=>"鉺", -"铓"=>"鋩", -"铔"=>"錏", -"铕"=>"銪", -"铖"=>"鋮", -"铗"=>"鋏", -"铘"=>"鋣", -"铙"=>"鐃", -"铚"=>"銍", -"铛"=>"鐺", -"铜"=>"銅", -"铝"=>"鋁", -"铞"=>"銱", -"铟"=>"銦", -"铠"=>"鎧", -"铡"=>"鍘", -"铢"=>"銖", -"铣"=>"銑", -"铤"=>"鋌", -"铥"=>"銩", -"铦"=>"銛", -"铧"=>"鏵", -"铨"=>"銓", -"铩"=>"鎩", -"铪"=>"鉿", -"铫"=>"銚", -"铬"=>"鉻", -"铭"=>"銘", -"铮"=>"錚", -"铯"=>"銫", -"铰"=>"鉸", -"铱"=>"銥", -"铲"=>"鏟", -"铳"=>"銃", -"铴"=>"鐋", -"铵"=>"銨", -"银"=>"銀", -"铷"=>"銣", -"铸"=>"鑄", -"铹"=>"鐒", -"铺"=>"鋪", -"铻"=>"鋙", -"铼"=>"錸", -"铽"=>"鋱", -"链"=>"鏈", -"铿"=>"鏗", -"销"=>"銷", -"锁"=>"鎖", -"锂"=>"鋰", -"锃"=>"鋥", -"锄"=>"鋤", -"锅"=>"鍋", -"锆"=>"鋯", -"锇"=>"鋨", -"锉"=>"銼", -"锊"=>"鋝", -"锋"=>"鋒", -"锌"=>"鋅", -"锍"=>"鋶", -"锎"=>"鐦", -"锏"=>"鐧", -"锑"=>"銻", -"锒"=>"鋃", -"锓"=>"鋟", -"锔"=>"鋦", -"锕"=>"錒", -"锖"=>"錆", -"锗"=>"鍺", -"锘"=>"鍩", -"错"=>"錯", -"锚"=>"錨", -"锛"=>"錛", -"锜"=>"錡", -"锝"=>"鍀", -"锞"=>"錁", -"锟"=>"錕", -"锠"=>"錩", -"锡"=>"錫", -"锢"=>"錮", -"锣"=>"鑼", -"锥"=>"錐", -"锦"=>"錦", -"锧"=>"鑕", -"锩"=>"錈", -"锪"=>"鍃", -"锫"=>"錇", -"锬"=>"錟", -"锭"=>"錠", -"键"=>"鍵", -"锯"=>"鋸", -"锰"=>"錳", -"锱"=>"錙", -"锲"=>"鍥", -"锳"=>"鍈", -"锴"=>"鍇", -"锵"=>"鏘", -"锶"=>"鍶", -"锷"=>"鍔", -"锸"=>"鍤", -"锹"=>"鍬", -"锺"=>"鍾", -"锻"=>"鍛", -"锼"=>"鎪", -"锽"=>"鍠", -"锾"=>"鍰", -"锿"=>"鎄", -"镀"=>"鍍", -"镁"=>"鎂", -"镂"=>"鏤", -"镃"=>"鎡", -"镄"=>"鐨", -"镅"=>"鎇", -"镆"=>"鏌", -"镇"=>"鎮", -"镈"=>"鎛", -"镉"=>"鎘", -"镊"=>"鑷", -"镋"=>"鎲", -"镍"=>"鎳", -"镎"=>"鎿", -"镏"=>"鎦", -"镐"=>"鎬", -"镑"=>"鎊", -"镒"=>"鎰", -"镓"=>"鎵", -"镔"=>"鑌", -"镕"=>"鎔", -"镖"=>"鏢", -"镗"=>"鏜", -"镘"=>"鏝", -"镙"=>"鏍", -"镚"=>"鏰", -"镛"=>"鏞", -"镜"=>"鏡", -"镝"=>"鏑", -"镞"=>"鏃", -"镟"=>"鏇", -"镠"=>"鏐", -"镡"=>"鐔", -"镣"=>"鐐", -"镤"=>"鏷", -"镥"=>"鑥", -"镦"=>"鐓", -"镧"=>"鑭", -"镨"=>"鐠", -"镩"=>"鑹", -"镪"=>"鏹", -"镫"=>"鐙", -"镬"=>"鑊", -"镭"=>"鐳", -"镮"=>"鐶", -"镯"=>"鐲", -"镰"=>"鐮", -"镱"=>"鐿", -"镲"=>"鑔", -"镳"=>"鑣", -"镴"=>"鑞", -"镵"=>"鑱", -"镶"=>"鑲", -"长"=>"長", -"门"=>"門", -"闩"=>"閂", -"闪"=>"閃", -"闫"=>"閆", -"闬"=>"閈", -"闭"=>"閉", -"问"=>"問", -"闯"=>"闖", -"闰"=>"閏", -"闱"=>"闈", -"闲"=>"閑", -"闳"=>"閎", -"间"=>"間", -"闵"=>"閔", -"闶"=>"閌", -"闷"=>"悶", -"闸"=>"閘", -"闹"=>"鬧", -"闺"=>"閨", -"闻"=>"聞", -"闼"=>"闥", -"闽"=>"閩", -"闾"=>"閭", -"闿"=>"闓", -"阀"=>"閥", -"阁"=>"閣", -"阂"=>"閡", -"阃"=>"閫", -"阄"=>"鬮", -"阆"=>"閬", -"阇"=>"闍", -"阈"=>"閾", -"阉"=>"閹", -"阊"=>"閶", -"阋"=>"鬩", -"阌"=>"閿", -"阍"=>"閽", -"阎"=>"閻", -"阏"=>"閼", -"阐"=>"闡", -"阑"=>"闌", -"阒"=>"闃", -"阓"=>"闠", -"阔"=>"闊", -"阕"=>"闋", -"阖"=>"闔", -"阗"=>"闐", -"阘"=>"闒", -"阙"=>"闕", -"阚"=>"闞", -"阛"=>"闤", -"队"=>"隊", -"阳"=>"陽", -"阴"=>"陰", -"阵"=>"陣", -"阶"=>"階", -"际"=>"際", -"陆"=>"陸", -"陇"=>"隴", -"陈"=>"陳", -"陉"=>"陘", -"陕"=>"陝", -"陧"=>"隉", -"陨"=>"隕", -"险"=>"險", -"随"=>"隨", -"隐"=>"隱", -"隶"=>"隸", -"隽"=>"雋", -"难"=>"難", -"雏"=>"雛", -"雠"=>"讎", -"雳"=>"靂", -"雾"=>"霧", -"霁"=>"霽", -"霡"=>"霢", -"霭"=>"靄", -"靓"=>"靚", -"静"=>"靜", -"靥"=>"靨", -"䩄"=>"靦", -"鞑"=>"韃", -"鞒"=>"鞽", -"鞯"=>"韉", -"韦"=>"韋", -"韧"=>"韌", -"韨"=>"韍", -"韩"=>"韓", -"韪"=>"韙", -"韫"=>"韞", -"韬"=>"韜", -"韵"=>"韻", -"页"=>"頁", -"顶"=>"頂", -"顷"=>"頃", -"顸"=>"頇", -"项"=>"項", -"顺"=>"順", -"顼"=>"頊", -"顽"=>"頑", -"顾"=>"顧", -"顿"=>"頓", -"颀"=>"頎", -"颁"=>"頒", -"颂"=>"頌", -"颃"=>"頏", -"预"=>"預", -"颅"=>"顱", -"领"=>"領", -"颇"=>"頗", -"颈"=>"頸", -"颉"=>"頡", -"颊"=>"頰", -"颋"=>"頲", -"颌"=>"頜", -"颍"=>"潁", -"颎"=>"熲", -"颏"=>"頦", -"颐"=>"頤", -"频"=>"頻", -"颒"=>"頮", -"颔"=>"頷", -"颕"=>"頴", -"颖"=>"穎", -"颗"=>"顆", -"题"=>"題", -"颙"=>"顒", -"颚"=>"顎", -"颛"=>"顓", -"额"=>"額", -"颞"=>"顳", -"颟"=>"顢", -"颠"=>"顛", -"颡"=>"顙", -"颢"=>"顥", -"颤"=>"顫", -"颥"=>"顬", -"颦"=>"顰", -"颧"=>"顴", -"风"=>"風", -"飏"=>"颺", -"飐"=>"颭", -"飑"=>"颮", -"飒"=>"颯", -"飓"=>"颶", -"飔"=>"颸", -"飕"=>"颼", -"飖"=>"颻", -"飗"=>"飀", -"飘"=>"飄", -"飙"=>"飆", -"飚"=>"飈", -"飞"=>"飛", -"飨"=>"饗", -"餍"=>"饜", -"饣"=>"飠", -"饤"=>"飣", -"饦"=>"飥", -"饧"=>"餳", -"饨"=>"飩", -"饩"=>"餼", -"饪"=>"飪", -"饫"=>"飫", -"饬"=>"飭", -"饭"=>"飯", -"饮"=>"飲", -"饯"=>"餞", -"饰"=>"飾", -"饱"=>"飽", -"饲"=>"飼", -"饳"=>"飿", -"饴"=>"飴", -"饵"=>"餌", -"饶"=>"饒", -"饷"=>"餉", -"饸"=>"餄", -"饹"=>"餎", -"饺"=>"餃", -"饻"=>"餏", -"饼"=>"餅", -"饽"=>"餑", -"饾"=>"餖", -"饿"=>"餓", -"馀"=>"餘", -"馁"=>"餒", -"馂"=>"餕", -"馃"=>"餜", -"馄"=>"餛", -"馅"=>"餡", -"馆"=>"館", -"馇"=>"餷", -"馈"=>"饋", -"馉"=>"餶", -"馊"=>"餿", -"馋"=>"饞", -"馌"=>"饁", -"馍"=>"饃", -"馎"=>"餺", -"馏"=>"餾", -"馐"=>"饈", -"馑"=>"饉", -"馒"=>"饅", -"馓"=>"饊", -"馔"=>"饌", -"馕"=>"饢", -"䯄"=>"騧", -"马"=>"馬", -"驭"=>"馭", -"驮"=>"馱", -"驯"=>"馴", -"驰"=>"馳", -"驱"=>"驅", -"驲"=>"馹", -"驳"=>"駁", -"驴"=>"驢", -"驵"=>"駔", -"驶"=>"駛", -"驷"=>"駟", -"驸"=>"駙", -"驹"=>"駒", -"驺"=>"騶", -"驻"=>"駐", -"驼"=>"駝", -"驽"=>"駑", -"驾"=>"駕", -"驿"=>"驛", -"骀"=>"駘", -"骁"=>"驍", -"骃"=>"駰", -"骄"=>"驕", -"骅"=>"驊", -"骆"=>"駱", -"骇"=>"駭", -"骈"=>"駢", -"骉"=>"驫", -"骊"=>"驪", -"骋"=>"騁", -"验"=>"驗", -"骍"=>"騂", -"骎"=>"駸", -"骏"=>"駿", -"骐"=>"騏", -"骑"=>"騎", -"骒"=>"騍", -"骓"=>"騅", -"骔"=>"騌", -"骕"=>"驌", -"骖"=>"驂", -"骗"=>"騙", -"骘"=>"騭", -"骙"=>"騤", -"骚"=>"騷", -"骛"=>"騖", -"骜"=>"驁", -"骝"=>"騮", -"骞"=>"騫", -"骟"=>"騸", -"骠"=>"驃", -"骡"=>"騾", -"骢"=>"驄", -"骣"=>"驏", -"骤"=>"驟", -"骥"=>"驥", -"骦"=>"驦", -"骧"=>"驤", -"髅"=>"髏", -"髋"=>"髖", -"髌"=>"髕", -"鬓"=>"鬢", -"魇"=>"魘", -"魉"=>"魎", -"鱼"=>"魚", -"鱽"=>"魛", -"鱾"=>"魢", -"鱿"=>"魷", -"鲀"=>"魨", -"鲁"=>"魯", -"鲂"=>"魴", -"鲃"=>"䰾", -"鲄"=>"魺", -"鲅"=>"鮁", -"鲆"=>"鮃", -"鲈"=>"鱸", -"鲉"=>"鮋", -"鲊"=>"鮓", -"鲋"=>"鮒", -"鲌"=>"鮊", -"鲍"=>"鮑", -"鲎"=>"鱟", -"鲏"=>"鮍", -"鲐"=>"鮐", -"鲑"=>"鮭", -"鲒"=>"鮚", -"鲓"=>"鮳", -"鲔"=>"鮪", -"鲕"=>"鮞", -"鲖"=>"鮦", -"鲗"=>"鰂", -"鲘"=>"鮜", -"鲙"=>"鱠", -"鲚"=>"鱭", -"鲛"=>"鮫", -"鲜"=>"鮮", -"鲝"=>"鮺", -"鲟"=>"鱘", -"鲠"=>"鯁", -"鲡"=>"鱺", -"鲢"=>"鰱", -"鲣"=>"鰹", -"鲤"=>"鯉", -"鲥"=>"鰣", -"鲦"=>"鰷", -"鲧"=>"鯀", -"鲨"=>"鯊", -"鲩"=>"鯇", -"鲪"=>"鮶", -"鲫"=>"鯽", -"鲬"=>"鯒", -"鲭"=>"鯖", -"鲮"=>"鯪", -"鲯"=>"鯕", -"鲰"=>"鯫", -"鲱"=>"鯡", -"鲲"=>"鯤", -"鲳"=>"鯧", -"鲴"=>"鯝", -"鲵"=>"鯢", -"鲶"=>"鯰", -"鲷"=>"鯛", -"鲸"=>"鯨", -"鲹"=>"鰺", -"鲺"=>"鯴", -"鲻"=>"鯔", -"鲼"=>"鱝", -"鲽"=>"鰈", -"鲾"=>"鰏", -"鲿"=>"鱨", -"鳀"=>"鯷", -"鳁"=>"鰮", -"鳂"=>"鰃", -"鳃"=>"鰓", -"鳅"=>"鰍", -"鳆"=>"鰒", -"鳇"=>"鰉", -"鳈"=>"鰁", -"鳉"=>"鱂", -"鳊"=>"鯿", -"鳋"=>"鰠", -"鳌"=>"鰲", -"鳍"=>"鰭", -"鳎"=>"鰨", -"鳏"=>"鰥", -"鳐"=>"鰩", -"鳑"=>"鰟", -"鳒"=>"鰜", -"鳓"=>"鰳", -"鳔"=>"鰾", -"鳕"=>"鱈", -"鳖"=>"鱉", -"鳗"=>"鰻", -"鳘"=>"鰵", -"鳙"=>"鱅", -"鳚"=>"䲁", -"鳛"=>"鰼", -"鳜"=>"鱖", -"鳝"=>"鱔", -"鳞"=>"鱗", -"鳟"=>"鱒", -"鳠"=>"鱯", -"鳡"=>"鱤", -"鳢"=>"鱧", -"鳣"=>"鱣", -"䴓"=>"鳾", -"䴕"=>"鴷", -"䴔"=>"鵁", -"䴖"=>"鶄", -"䴗"=>"鶪", -"䴘"=>"鷈", -"䴙"=>"鷿", -"㶉"=>"鸂", -"鸟"=>"鳥", -"鸠"=>"鳩", -"鸢"=>"鳶", -"鸣"=>"鳴", -"鸤"=>"鳲", -"鸥"=>"鷗", -"鸦"=>"鴉", -"鸧"=>"鶬", -"鸨"=>"鴇", -"鸩"=>"鴆", -"鸪"=>"鴣", -"鸫"=>"鶇", -"鸬"=>"鸕", -"鸭"=>"鴨", -"鸮"=>"鴞", -"鸯"=>"鴦", -"鸰"=>"鴒", -"鸱"=>"鴟", -"鸲"=>"鴝", -"鸳"=>"鴛", -"鸴"=>"鷽", -"鸵"=>"鴕", -"鸶"=>"鷥", -"鸷"=>"鷙", -"鸸"=>"鴯", -"鸹"=>"鴰", -"鸺"=>"鵂", -"鸻"=>"鴴", -"鸼"=>"鵃", -"鸽"=>"鴿", -"鸾"=>"鸞", -"鸿"=>"鴻", -"鹀"=>"鵐", -"鹁"=>"鵓", -"鹂"=>"鸝", -"鹃"=>"鵑", -"鹄"=>"鵠", -"鹅"=>"鵝", -"鹆"=>"鵒", -"鹇"=>"鷳", -"鹈"=>"鵜", -"鹉"=>"鵡", -"鹊"=>"鵲", -"鹋"=>"鶓", -"鹌"=>"鵪", -"鹍"=>"鵾", -"鹎"=>"鵯", -"鹏"=>"鵬", -"鹐"=>"鵮", -"鹑"=>"鶉", -"鹒"=>"鶊", -"鹓"=>"鵷", -"鹔"=>"鷫", -"鹕"=>"鶘", -"鹖"=>"鶡", -"鹗"=>"鶚", -"鹘"=>"鶻", -"鹙"=>"鶖", -"鹛"=>"鶥", -"鹜"=>"鶩", -"鹝"=>"鷊", -"鹞"=>"鷂", -"鹟"=>"鶲", -"鹠"=>"鶹", -"鹡"=>"鶺", -"鹢"=>"鷁", -"鹣"=>"鶼", -"鹤"=>"鶴", -"鹥"=>"鷖", -"鹦"=>"鸚", -"鹧"=>"鷓", -"鹨"=>"鷚", -"鹩"=>"鷯", -"鹪"=>"鷦", -"鹫"=>"鷲", -"鹬"=>"鷸", -"鹭"=>"鷺", -"鹯"=>"鸇", -"鹰"=>"鷹", -"鹱"=>"鸌", -"鹲"=>"鸏", -"鹳"=>"鸛", -"鹴"=>"鸘", -"鹾"=>"鹺", -"麦"=>"麥", -"麸"=>"麩", -"黄"=>"黃", -"黉"=>"黌", -"黡"=>"黶", -"黩"=>"黷", -"黪"=>"黲", -"黾"=>"黽", -"鼋"=>"黿", -"鼍"=>"鼉", -"鼗"=>"鞀", -"鼹"=>"鼴", -"齐"=>"齊", -"齑"=>"齏", -"齿"=>"齒", -"龀"=>"齔", -"龁"=>"齕", -"龂"=>"齗", -"龃"=>"齟", -"龄"=>"齡", -"龅"=>"齙", -"龆"=>"齠", -"龇"=>"齜", -"龈"=>"齦", -"龉"=>"齬", -"龊"=>"齪", -"龋"=>"齲", -"龌"=>"齷", -"龙"=>"龍", -"龚"=>"龔", -"龛"=>"龕", -"龟"=>"龜", - -"0多只" => "0多隻", -"0天后" => "0天後", -"1天后" => "1天後", -"2天后" => "2天後", -"3天后" => "3天後", -"4天后" => "4天後", -"5天后" => "5天後", -"6天后" => "6天後", -"7天后" => "7天後", -"8天后" => "8天後", -"9天后" => "9天後", -"一干二净" => "一乾二淨", -"一并" => "一併", -"一前一后" => "一前一後", -"一划" => "一劃", -"一口钟" => "一口鐘", -"一地里" => "一地裡", -"一伙" => "一夥", -"一天后" => "一天後", -"一干人" => "一干人", -"一别头" => "一彆頭", -"一树百获" => "一樹百穫", -"一准" => "一準", -"一争两丑" => "一爭兩醜", -"一箭双雕" => "一箭雙鵰", -"一扎" => "一紮", -"一冲" => "一衝", -"一锅面" => "一鍋麵", -"一只" => "一隻", -"一发千钧" => "一髮千鈞", -"一哄而散" => "一鬨而散", -"丁丁当当" => "丁丁當當", -"七划" => "七劃", -"七天后" => "七天後", -"七情六欲" => "七情六慾", -"七扎" => "七紮", -"七只" => "七隻", -"万俟" => "万俟", -"万旗" => "万旗", -"三天后" => "三天後", -"三棱锥" => "三稜錐", -"三扎" => "三紮", -"三统历" => "三統曆", -"三复" => "三複", -"三只" => "三隻", -"三余" => "三餘", -"上梁" => "上樑", -"上签名" => "上簽名", -"上签字" => "上簽字", -"上签写" => "上簽寫", -"上签收" => "上簽收", -"上签" => "上籤", -"上药" => "上藥", -"下于" => "下於", -"下签" => "下籤", -"下药" => "下藥", -"下余" => "下餘", -"不下于" => "不下於", -"不亚于" => "不亞於", -"不占" => "不佔", -"不前不后" => "不前不後", -"不可救药" => "不可救藥", -"不同于" => "不同於", -"不嫌母丑" => "不嫌母醜", -"不寒而栗" => "不寒而慄", -"不屑于" => "不屑於", -"不干不净" => "不幹不淨", -"不干性油" => "不幹性油", -"不采" => "不採", -"不断发" => "不斷發", -"不准" => "不準", -"不为牛后" => "不為牛後", -"不知就里" => "不知就裡", -"不知所云" => "不知所云", -"不谷" => "不穀", -"不绝于耳" => "不絕於耳", -"不致于" => "不致於", -"不良于行" => "不良於行", -"不药而癒" => "不藥而癒", -"不讬" => "不託", -"不逊于" => "不遜於", -"不丑" => "不醜", -"不锈钢" => "不鏽鋼", -"世界杯" => "世界盃", -"丢丑" => "丟醜", -"并存着" => "並存著", -"并于" => "並於", -"并发动" => "並發動", -"并发展" => "並發展", -"并发现" => "並發現", -"并发表" => "並發表", -"中仑" => "中崙", -"中岳" => "中嶽", -"中于" => "中於", -"中美发表" => "中美發表", -"中药" => "中藥", -"丰仪" => "丰儀", -"丰南" => "丰南", -"丰台" => "丰台", -"丰姿" => "丰姿", -"丰度" => "丰度", -"丰情" => "丰情", -"丰标不凡" => "丰標不凡", -"丰神" => "丰神", -"丰茸" => "丰茸", -"丰采" => "丰采", -"丰韵" => "丰韵", -"丰韵" => "丰韻", -"丸药" => "丸藥", -"丹药" => "丹藥", -"主仆" => "主僕", -"主干" => "主幹", -"么么小丑" => "么麼小丑", -"之后" => "之後", -"之于" => "之於", -"之余" => "之餘", -"九世之雠" => "九世之讎", -"九划" => "九劃", -"九天后" => "九天後", -"九谷" => "九穀", -"九扎" => "九紮", -"九只" => "九隻", -"乳臭未干" => "乳臭未乾", -"干上" => "乾上", -"干干" => "乾乾", -"干干儿的" => "乾乾兒的", -"干干净净" => "乾乾淨淨", -"干了" => "乾了", -"干井" => "乾井", -"干个" => "乾個", -"干儿" => "乾兒", -"干儿子" => "乾兒子", -"干冰" => "乾冰", -"干冷" => "乾冷", -"干刻版" => "乾刻版", -"干剥剥" => "乾剝剝", -"干卦" => "乾卦", -"干吊着下巴" => "乾吊著下巴", -"干和" => "乾和", -"干咳" => "乾咳", -"干咽" => "乾咽", -"干哥" => "乾哥", -"干哭" => "乾哭", -"干唱" => "乾唱", -"干啼" => "乾啼", -"干乔" => "乾喬", -"干呕" => "乾嘔", -"干哕" => "乾噦", -"干嚎" => "乾嚎", -"干回付" => "乾回付", -"干圆洁净" => "乾圓潔淨", -"干地" => "乾地", -"干坤" => "乾坤", -"干坞" => "乾塢", -"干女" => "乾女", -"干女儿" => "乾女兒", -"干奴才" => "乾奴才", -"干妹" => "乾妹", -"干姊" => "乾姊", -"干娘" => "乾娘", -"干妈" => "乾媽", -"干子" => "乾子", -"干季" => "乾季", -"干尸" => "乾屍", -"干屎橛" => "乾屎橛", -"干巴" => "乾巴", -"干巴巴" => "乾巴巴", -"干式" => "乾式", -"干弟" => "乾弟", -"干得" => "乾得", -"干急" => "乾急", -"干性" => "乾性", -"干打雷" => "乾打雷", -"干折" => "乾折", -"干掉" => "乾掉", -"干撂台" => "乾撂台", -"干撇下" => "乾撇下", -"干擦" => "乾擦", -"干支剌" => "乾支剌", -"干支支" => "乾支支", -"干敲梆子不卖油" => "乾敲梆子不賣油", -"干料" => "乾料", -"干旱" => "乾旱", -"干暖" => "乾暖", -"干材" => "乾材", -"干村沙" => "乾村沙", -"干杯" => "乾杯", -"干果" => "乾果", -"干枯" => "乾枯", -"干柴" => "乾柴", -"干柴烈火" => "乾柴烈火", -"干梅" => "乾梅", -"干死" => "乾死", -"干池" => "乾池", -"干没" => "乾沒", -"干洗" => "乾洗", -"干涸" => "乾涸", -"干凉" => "乾涼", -"干净" => "乾淨", -"干渠" => "乾渠", -"干渴" => "乾渴", -"干沟" => "乾溝", -"干漆" => "乾漆", -"干涩" => "乾澀", -"干湿" => "乾濕", -"干熬" => "乾熬", -"干热" => "乾熱", -"干灯盏" => "乾燈盞", -"干燥" => "乾燥", -"干爸" => "乾爸", -"干爹" => "乾爹", -"干爽" => "乾爽", -"干片" => "乾片", -"干生受" => "乾生受", -"干生子" => "乾生子", -"干产" => "乾產", -"干田" => "乾田", -"干疥" => "乾疥", -"干瘦" => "乾瘦", -"干瘪" => "乾癟", -"干癣" => "乾癬", -"干白儿" => "乾白兒", -"干的" => "乾的", -"干眼" => "乾眼", -"干眼病" => "乾眼病", -"干瞪眼" => "乾瞪眼", -"干礼" => "乾禮", -"干稿" => "乾稿", -"干笑" => "乾笑", -"干等" => "乾等", -"干篾片" => "乾篾片", -"干粉" => "乾粉", -"干粮" => "乾糧", -"干结" => "乾結", -"干丝" => "乾絲", -"干绷" => "乾繃", -"干耗" => "乾耗", -"干肉片" => "乾肉片", -"干股" => "乾股", -"干肥" => "乾肥", -"干脆" => "乾脆", -"干花" => "乾花", -"干刍" => "乾芻", -"干苔" => "乾苔", -"干茨腊" => "乾茨臘", -"干茶钱" => "乾茶錢", -"干草" => "乾草", -"干菜" => "乾菜", -"干落" => "乾落", -"干着" => "乾著", -"干着急" => "乾著急", -"干姜" => "乾薑", -"干薪" => "乾薪", -"干虔" => "乾虔", -"干号" => "乾號", -"干衣" => "乾衣", -"干裂" => "乾裂", -"干亲" => "乾親", -"干贝" => "乾貝", -"干货" => "乾貨", -"干躁" => "乾躁", -"干逼" => "乾逼", -"干酪" => "乾酪", -"干酵母" => "乾酵母", -"干醋" => "乾醋", -"干量" => "乾量", -"干阿奶" => "乾阿奶", -"干隆" => "乾隆", -"干雷" => "乾雷", -"干电" => "乾電", -"干电池" => "乾電池", -"干霍乱" => "乾霍亂", -"干颡" => "乾顙", -"干台" => "乾颱", -"干饭" => "乾飯", -"干馆" => "乾館", -"干糇" => "乾餱", -"干馏" => "乾餾", -"干鱼" => "乾魚", -"干鲜" => "乾鮮", -"干面" => "乾麵", -"乱发" => "亂髮", -"乱哄" => "亂鬨", -"乱哄不过来" => "亂鬨不過來", -"事后" => "事後", -"二不棱登" => "二不稜登", -"二划" => "二劃", -"二天后" => "二天後", -"二缶钟惑" => "二缶鐘惑", -"二里头" => "二里頭", -"二只" => "二隻", -"于余曲折" => "于餘曲折", -"云乎" => "云乎", -"云云" => "云云", -"云为" => "云為", -"云然" => "云然", -"云尔" => "云爾", -"互于" => "互於", -"五划" => "五劃", -"五天后" => "五天後", -"五岳" => "五嶽", -"五谷" => "五穀", -"五扎" => "五紮", -"五行生克" => "五行生剋", -"五只" => "五隻", -"五出" => "五齣", -"井干摧败" => "井榦摧敗", -"亚于" => "亞於", -"交于" => "交於", -"交讬" => "交託", -"交游广阔" => "交遊廣闊", -"交哄" => "交鬨", -"亮丑" => "亮醜", -"亮钟" => "亮鐘", -"人云亦云" => "人云亦云", -"人参加" => "人參加", -"人参展" => "人參展", -"人参战" => "人參戰", -"人参拜" => "人參拜", -"人参政" => "人參政", -"人参照" => "人參照", -"人参看" => "人參看", -"人参禅" => "人參禪", -"人参考" => "人參考", -"人参与" => "人參與", -"人参见" => "人參見", -"人参观" => "人參觀", -"人参谋" => "人參謀", -"人参议" => "人參議", -"人参赞" => "人參贊", -"人参透" => "人參透", -"人参选" => "人參選", -"人参酌" => "人參酌", -"人参阅" => "人參閱", -"人后" => "人後", -"人欲" => "人慾", -"人物志" => "人物誌", -"人参" => "人蔘", -"什锦面" => "什錦麵", -"什么" => "什麼", -"今后" => "今後", -"介于" => "介於", -"付讬" => "付託", -"仙药" => "仙藥", -"以后" => "以後", -"任教于" => "任教於", -"任于" => "任於", -"仿制" => "仿製", -"企划" => "企劃", -"伊府面" => "伊府麵", -"伊斯兰教历" => "伊斯蘭教曆", -"伊斯兰历" => "伊斯蘭曆", -"伊郁" => "伊鬱", -"伏几" => "伏几", -"伙头" => "伙頭", -"似于" => "似於", -"但云" => "但云", -"布于" => "佈於", -"位于" => "位於", -"低于" => "低於", -"占上风" => "佔上風", -"占下" => "佔下", -"占了" => "佔了", -"占位" => "佔位", -"占住" => "佔住", -"占占" => "佔佔", -"占便宜" => "佔便宜", -"占个" => "佔個", -"占优势" => "佔優勢", -"占先" => "佔先", -"占光" => "佔光", -"占到" => "佔到", -"占去" => "佔去", -"占取" => "佔取", -"占在" => "佔在", -"占地" => "佔地", -"占多数" => "佔多數", -"占好" => "佔好", -"占得" => "佔得", -"占掉" => "佔掉", -"占据" => "佔據", -"占有" => "佔有", -"占满" => "佔滿", -"占为" => "佔為", -"占用" => "佔用", -"占尽" => "佔盡", -"占线" => "佔線", -"占起" => "佔起", -"占超过" => "佔超過", -"占过" => "佔過", -"占领" => "佔領", -"余光中" => "余光中", -"余光生" => "余光生", -"佛罗棱萨" => "佛羅稜薩", -"作奸犯科" => "作姦犯科", -"作准" => "作準", -"作庄" => "作莊", -"你才子发昏" => "你纔子發昏", -"并一不二" => "併一不二", -"并入" => "併入", -"并兼" => "併兼", -"并到" => "併到", -"并合" => "併合", -"并名" => "併名", -"并吞" => "併吞", -"并拢" => "併攏", -"并案" => "併案", -"并流" => "併流", -"并火" => "併火", -"并为" => "併為", -"并产" => "併產", -"并当" => "併當", -"并叠" => "併疊", -"并发" => "併發", -"并科" => "併科", -"并网" => "併網", -"并线" => "併線", -"并肩子" => "併肩子", -"并购" => "併購", -"并除" => "併除", -"并骨" => "併骨", -"来于" => "來於", -"来自于" => "來自於", -"来复" => "來複", -"侍仆" => "侍僕", -"供制" => "供製", -"依依不舍" => "依依不捨", -"依讬" => "依託", -"依附于" => "依附於", -"侵占" => "侵佔", -"侵并" => "侵併", -"便于" => "便於", -"便药" => "便藥", -"系数" => "係數", -"系为" => "係為", -"保险柜" => "保險柜", -"信讬" => "信託", -"修改后" => "修改後", -"修胡刀" => "修鬍刀", -"俯冲" => "俯衝", -"个里" => "個裡", -"幸免" => "倖免", -"幸存" => "倖存", -"幸幸" => "倖幸", -"倛丑" => "倛醜", -"借助于" => "借助於", -"借着" => "借著", -"借讬" => "借託", -"倦游" => "倦遊", -"假力于人" => "假力於人", -"假药" => "假藥", -"假讬" => "假託", -"假发" => "假髮", -"偎干" => "偎乾", -"偎干就湿" => "偎乾就濕", -"偏后" => "偏後", -"偏于" => "偏於", -"做庄" => "做莊", -"停停当当" => "停停當當", -"停征" => "停徵", -"停制" => "停製", -"偷鸡不着" => "偷雞不著", -"伪药" => "偽藥", -"备注" => "備註", -"家伙" => "傢伙", -"家俱" => "傢俱", -"家具" => "傢具", -"催并" => "催併", -"佣人" => "傭人", -"佣兵" => "傭兵", -"佣工" => "傭工", -"佣懒" => "傭懶", -"佣书" => "傭書", -"佣金" => "傭金", -"伤痕累累" => "傷痕纍纍", -"傻里傻气" => "傻裡傻氣", -"倾向于" => "傾向於", -"倾家荡产" => "傾家蕩產", -"倾复" => "傾複", -"仆人" => "僕人", -"仆使" => "僕使", -"仆仆" => "僕僕", -"仆仆风尘" => "僕僕風塵", -"仆僮" => "僕僮", -"仆吏" => "僕吏", -"仆固怀恩" => "僕固懷恩", -"仆夫" => "僕夫", -"仆姑" => "僕姑", -"仆妇" => "僕婦", -"仆射" => "僕射", -"仆少" => "僕少", -"仆役" => "僕役", -"仆从" => "僕從", -"仆憎" => "僕憎", -"仆欧" => "僕歐", -"仆程" => "僕程", -"仆虽罢驽" => "僕雖罷駑", -"侥幸" => "僥倖", -"僮仆" => "僮僕", -"雇主" => "僱主", -"雇人" => "僱人", -"雇佣" => "僱佣", -"雇到" => "僱到", -"雇员" => "僱員", -"雇工" => "僱工", -"雇用" => "僱用", -"雇农" => "僱農", -"仪范" => "儀範", -"仪表" => "儀錶", -"亿多只" => "億多隻", -"亿天后" => "億天後", -"亿只" => "億隻", -"俭朴" => "儉樸", -"儒略改革历" => "儒略改革曆", -"儒略历" => "儒略曆", -"尽尽" => "儘儘", -"尽先" => "儘先", -"尽其所有" => "儘其所有", -"尽力" => "儘力", -"尽快" => "儘快", -"尽早" => "儘早", -"尽是" => "儘是", -"尽管" => "儘管", -"尽速" => "儘速", -"优于" => "優於", -"优游" => "優遊", -"兀术" => "兀朮", -"元凶" => "元兇", -"充饥" => "充饑", -"凶器" => "兇器", -"凶徒" => "兇徒", -"凶手" => "兇手", -"凶案" => "兇案", -"凶残" => "兇殘", -"凶杀" => "兇殺", -"先占" => "先佔", -"先后" => "先後", -"先忧后乐" => "先憂後樂", -"先采" => "先採", -"先攻后守" => "先攻後守", -"先于" => "先於", -"先盛后衰" => "先盛後衰", -"先礼后兵" => "先禮後兵", -"先义后利" => "先義後利", -"先苦后甘" => "先苦後甘", -"先赢后输" => "先贏後輸", -"先进后出" => "先進後出", -"光采" => "光採", -"光致致" => "光緻緻", -"克药" => "克藥", -"克复" => "克複", -"免于" => "免於", -"党参" => "党參", -"党太尉" => "党太尉", -"党进" => "党進", -"党项" => "党項", -"入夜后" => "入夜後", -"入伙" => "入夥", -"内制" => "內製", -"内斗" => "內鬥", -"内哄" => "內鬨", -"全干" => "全乾", -"两天后" => "兩天後", -"两扎" => "兩紮", -"两只" => "兩隻", -"八天后" => "八天後", -"八字胡" => "八字鬍", -"八扎" => "八紮", -"八只" => "八隻", -"公仔面" => "公仔麵", -"公仆" => "公僕", -"公干" => "公幹", -"公历" => "公曆", -"公诸于世" => "公諸於世", -"公厘" => "公釐", -"公余" => "公餘", -"六划" => "六劃", -"六天后" => "六天後", -"六扎" => "六紮", -"六冲" => "六衝", -"六只" => "六隻", -"六出" => "六齣", -"其后" => "其後", -"其次辟地" => "其次辟地", -"其余" => "其餘", -"典范" => "典範", -"兼并" => "兼併", -"冉有仆" => "冉有僕", -"再于" => "再於", -"冤雠" => "冤讎", -"冥蒙" => "冥濛", -"冬山庄" => "冬山庄", -"冬游" => "冬遊", -"冶游" => "冶遊", -"冷面相" => "冷面相", -"冷面" => "冷麵", -"凌蒙初" => "凌濛初", -"凌藉" => "凌藉", -"几几" => "几几", -"几案" => "几案", -"几丝" => "几絲", -"凡于" => "凡於", -"凹洞里" => "凹洞裡", -"出乖露丑" => "出乖露醜", -"出于" => "出於", -"出自于" => "出自於", -"出谋划策" => "出謀劃策", -"出游" => "出遊", -"出丑" => "出醜", -"出锤" => "出鎚", -"刀削面" => "刀削麵", -"分布" => "分佈", -"分占" => "分佔", -"分钟" => "分鐘", -"刑余" => "刑餘", -"划着" => "划著", -"划着走" => "划著走", -"划龙舟" => "划龍舟", -"别后" => "別後", -"别日南鸿才北去" => "別日南鴻纔北去", -"别致" => "別緻", -"别着" => "別著", -"别辟" => "別闢", -"别只" => "別隻", -"利欲" => "利慾", -"利于" => "利於", -"刮着" => "刮著", -"刮风下雪倒便宜" => "刮風下雪倒便宜", -"刮胡刀" => "刮鬍刀", -"制签" => "制籤", -"刺绣" => "刺繡", -"刻划" => "刻劃", -"刻于" => "刻於", -"刻钟" => "刻鐘", -"剃发" => "剃髮", -"剃须" => "剃鬚", -"削发" => "削髮", -"削面" => "削麵", -"克制" => "剋制", -"克星" => "剋星", -"克死" => "剋死", -"克薄" => "剋薄", -"前仰后合" => "前仰後合", -"前倨后恭" => "前倨後恭", -"前呼后拥" => "前呼後擁", -"前后" => "前後", -"前思后想" => "前思後想", -"前挽后推" => "前挽後推", -"前短后长" => "前短後長", -"刚干" => "剛乾", -"刚雇" => "剛僱", -"刚才一载" => "剛纔一載", -"剩余" => "剩餘", -"剪牡丹喂牛" => "剪牡丹喂牛", -"剪䌽" => "剪綵", -"剪发" => "剪髮", -"割舍" => "割捨", -"创制" => "創製", -"划一" => "劃一", -"划上" => "劃上", -"划下" => "劃下", -"划了" => "劃了", -"划出" => "劃出", -"划分" => "劃分", -"划到" => "劃到", -"划划" => "劃劃", -"划去" => "劃去", -"划在" => "劃在", -"划地" => "劃地", -"划定" => "劃定", -"划得" => "劃得", -"划成" => "劃成", -"划掉" => "劃掉", -"划拨" => "劃撥", -"划时代" => "劃時代", -"划款" => "劃款", -"划归" => "劃歸", -"划法" => "劃法", -"划清" => "劃清", -"划界" => "劃界", -"划破" => "劃破", -"划线" => "劃線", -"划足" => "劃足", -"划开" => "劃開", -"力争上游" => "力爭上遊", -"功致" => "功緻", -"加害于" => "加害於", -"加工余量" => "加工餘量", -"加卷" => "加捲", -"加于" => "加於", -"加药" => "加藥", -"加注" => "加註", -"劣于" => "劣於", -"助于" => "助於", -"劫余" => "劫餘", -"勃郁" => "勃鬱", -"勇于" => "勇於", -"动荡" => "動蕩", -"胜于" => "勝於", -"劳力士表" => "勞力士錶", -"勤仆" => "勤僕", -"勤朴" => "勤樸", -"勾干" => "勾幹", -"勾心斗角" => "勾心鬥角", -"勿施于人" => "勿施於人", -"包谷" => "包穀", -"包扎" => "包紮", -"北岳" => "北嶽", -"北回线" => "北迴線", -"北回铁路" => "北迴鐵路", -"匡复" => "匡複", -"匪干" => "匪幹", -"匿于" => "匿於", -"区划" => "區劃", -"十划" => "十劃", -"十多只" => "十多隻", -"十天后" => "十天後", -"十卷" => "十捲", -"十扎" => "十紮", -"十只" => "十隻", -"十出" => "十齣", -"千多只" => "千多隻", -"千天后" => "千天後", -"千扎" => "千紮", -"千丝万缕" => "千絲萬縷", -"千回百折" => "千迴百折", -"千回百转" => "千迴百轉", -"千钧一发" => "千鈞一髮", -"千只" => "千隻", -"午后" => "午後", -"半于" => "半於", -"半只" => "半隻", -"南岳" => "南嶽", -"南筑" => "南筑", -"南回线" => "南迴線", -"南回铁路" => "南迴鐵路", -"南游" => "南遊", -"博汇" => "博彙", -"博采" => "博採", -"印累绶若" => "印纍綬若", -"印制" => "印製", -"危于" => "危於", -"卷发" => "卷髮", -"卷须" => "卷鬚", -"厂部" => "厂部", -"原子钟" => "原子鐘", -"原于" => "原於", -"历物之意" => "厤物之意", -"参与" => "參与", -"参与者" => "參与者", -"参合" => "參合", -"参考价值" => "參考價值", -"参与" => "參與", -"参与人员" => "參與人員", -"参与制" => "參與制", -"参与感" => "參與感", -"参与者" => "參與者", -"参观团" => "參觀團", -"参观团体" => "參觀團體", -"参阅" => "參閱", -"及于" => "及於", -"反于" => "反於", -"反朴" => "反樸", -"反冲" => "反衝", -"反复制" => "反複製", -"反复" => "反覆", -"取信于" => "取信於", -"取舍" => "取捨", -"取材于" => "取材於", -"取决于" => "取決於", -"取法于" => "取法於", -"受人之讬" => "受人之託", -"受制于人" => "受制於人", -"受讬" => "受託", -"受阻于" => "受阻於", -"口干" => "口乾", -"口燥脣干" => "口燥脣乾", -"口腹之欲" => "口腹之慾", -"口血未干" => "口血未乾", -"口里" => "口裡", -"古柯硷" => "古柯鹼", -"古朴" => "古樸", -"另于" => "另於", -"另辟" => "另闢", -"叩钟" => "叩鐘", -"只占" => "只佔", -"只采" => "只採", -"只冲" => "只衝", -"叮当" => "叮噹", -"可于" => "可於", -"可紧可松" => "可緊可鬆", -"台后" => "台後", -"台历" => "台曆", -"台制" => "台製", -"右后" => "右後", -"叶恭弘" => "叶恭弘", -"叶 恭弘" => "叶 恭弘", -"叶 恭弘" => "叶 恭弘", -"叶音" => "叶音", -"叶韵" => "叶韻", -"吃板刀面" => "吃板刀麵", -"吃着不尽" => "吃著不盡", -"吃姜" => "吃薑", -"吃药" => "吃藥", -"吃里扒外" => "吃裡扒外", -"吃里爬外" => "吃裡爬外", -"吃豆干" => "吃豆乾", -"吃辣面" => "吃辣麵", -"吃错药" => "吃錯藥", -"各辟" => "各闢", -"合并" => "合併", -"合伙" => "合夥", -"合采" => "合採", -"合于" => "合於", -"合历" => "合曆", -"合着" => "合著", -"合着者" => "合著者", -"吊带裤" => "吊帶褲", -"吊挂着" => "吊掛著", -"吊着" => "吊著", -"吊裤" => "吊褲", -"吊裤带" => "吊褲帶", -"吊钟" => "吊鐘", -"同伙" => "同夥", -"同于" => "同於", -"名闻于世" => "名聞於世", -"后发座" => "后髮座", -"向后" => "向後", -"向着" => "向著", -"吞并" => "吞併", -"吟游" => "吟遊", -"吹干" => "吹乾", -"吹发" => "吹髮", -"呆致致" => "呆緻緻", -"呆里呆气" => "呆裡呆氣", -"周历" => "周曆", -"周杰伦" => "周杰倫", -"周游" => "周遊", -"呼吁" => "呼籲", -"咬姜呷醋" => "咬薑呷醋", -"咯当" => "咯噹", -"咳嗽药" => "咳嗽藥", -"哀吊" => "哀弔", -"品汇" => "品彙", -"员山庄" => "員山庄", -"哪里" => "哪裡", -"哭脏" => "哭髒", -"唇干" => "唇乾", -"唱游" => "唱遊", -"唾面自干" => "唾面自乾", -"唾余" => "唾餘", -"商历" => "商曆", -"问政于民" => "問政於民", -"问道于盲" => "問道於盲", -"啷当" => "啷噹", -"善后" => "善後", -"善于" => "善於", -"喜形于色" => "喜形於色", -"喧哄" => "喧鬨", -"丧钟" => "喪鐘", -"单干" => "單幹", -"单打独斗" => "單打獨鬥", -"单只" => "單隻", -"嗑药" => "嗑藥", -"嗣后" => "嗣後", -"嘉谷" => "嘉穀", -"嘴里" => "嘴裡", -"恶心" => "噁心", -"噙齿戴发" => "噙齒戴髮", -"当啷" => "噹啷", -"当当" => "噹噹", -"噜苏" => "嚕囌", -"向导" => "嚮導", -"向往" => "嚮往", -"向应" => "嚮應", -"向迩" => "嚮邇", -"严于" => "嚴於", -"严丝合缝" => "嚴絲合縫", -"嚼谷" => "嚼穀", -"囉囉苏苏" => "囉囉囌囌", -"囉苏" => "囉囌", -"嘱讬" => "囑託", -"四分历" => "四分曆", -"四天后" => "四天後", -"四舍五入" => "四捨五入", -"四扎" => "四紮", -"四只" => "四隻", -"四出" => "四齣", -"回采" => "回採", -"回历" => "回曆", -"回丝" => "回絲", -"回着" => "回著", -"回荡" => "回蕩", -"回游" => "回遊", -"因于" => "因於", -"困于" => "困於", -"困兽之斗" => "困獸之鬥", -"困兽犹斗" => "困獸猶鬥", -"固于" => "固於", -"囿于" => "囿於", -"囿于一时" => "囿於一時", -"囿于成见" => "囿於成見", -"圈子里" => "圈子裡", -"圈梁" => "圈樑", -"圈里" => "圈裡", -"国之桢干" => "國之楨榦", -"国于" => "國於", -"国历" => "國曆", -"国历代" => "國歷代", -"国历史" => "國歷史", -"国雠" => "國讎", -"园里" => "園裡", -"园游会" => "園遊會", -"图里" => "圖裡", -"土里" => "土裡", -"土制" => "土製", -"在于" => "在於", -"地志" => "地誌", -"地丑德齐" => "地醜德齊", -"坏于" => "坏於", -"坐钟" => "坐鐘", -"坑里" => "坑裡", -"坤范" => "坤範", -"坦荡" => "坦蕩", -"坱郁" => "坱鬱", -"垂直于" => "垂直於", -"垂发" => "垂髮", -"型范" => "型範", -"埃及历" => "埃及曆", -"城里" => "城裡", -"基干" => "基幹", -"基于" => "基於", -"基准" => "基準", -"坚致" => "堅緻", -"涂着" => "塗著", -"涂药" => "塗藥", -"塞耳盗钟" => "塞耳盜鐘", -"塞药" => "塞藥", -"墓志" => "墓誌", -"增辟" => "增闢", -"墨沈" => "墨沈", -"堕胎药" => "墮胎藥", -"垦复" => "墾複", -"垦辟" => "墾闢", -"垄断价格" => "壟斷價格", -"垄断资产" => "壟斷資產", -"垄断集团" => "壟斷集團", -"壮面" => "壯麵", -"壹郁" => "壹鬱", -"壶里" => "壺裡", -"壸范" => "壼範", -"寿面" => "壽麵", -"夏天里" => "夏天裡", -"夏历" => "夏曆", -"夏历史" => "夏歷史", -"夏游" => "夏遊", -"外强中干" => "外強中乾", -"外制" => "外製", -"多划" => "多劃", -"多只是" => "多只是", -"多天后" => "多天後", -"多于" => "多於", -"多冲" => "多衝", -"多丑" => "多醜", -"多只" => "多隻", -"多余" => "多餘", -"多么" => "多麼", -"夜光表" => "夜光錶", -"夜里" => "夜裡", -"夜游" => "夜遊", -"梦里" => "夢裡", -"梦游" => "夢遊", -"伙伴" => "夥伴", -"伙友" => "夥友", -"伙同" => "夥同", -"伙众" => "夥眾", -"伙计" => "夥計", -"大伙" => "大夥", -"大干" => "大幹", -"大批涌到" => "大批湧到", -"大于" => "大於", -"大明历" => "大明曆", -"大历" => "大曆", -"大梁" => "大樑", -"大目干连" => "大目乾連", -"大衍历" => "大衍曆", -"大言非夸" => "大言非夸", -"大丑" => "大醜", -"大锤" => "大鎚", -"大只" => "大隻", -"天干物燥" => "天乾物燥", -"天克地冲" => "天克地衝", -"天后" => "天後", -"0天后" => "0天後", -"天文钟" => "天文鐘", -"天然硷" => "天然鹼", -"天翻地复" => "天翻地覆", -"天复地载" => "天覆地載", -"太仆" => "太僕", -"太初历" => "太初曆", -"夯干" => "夯幹", -"失信于人" => "失信於人", -"失于" => "失於", -"夸人" => "夸人", -"夸克" => "夸克", -"夸姣" => "夸姣", -"夸容" => "夸容", -"夸毗" => "夸毗", -"夸父" => "夸父", -"夸特" => "夸特", -"夸丽" => "夸麗", -"奇丑" => "奇醜", -"奏折" => "奏摺", -"奏于" => "奏於", -"夺斗" => "奪鬥", -"奋斗" => "奮鬥", -"女佣" => "女傭", -"女仆" => "女僕", -"奴仆" => "奴僕", -"好干" => "好乾", -"好家伙" => "好傢夥", -"好于" => "好於", -"好签" => "好籤", -"好丑" => "好醜", -"如于" => "如於", -"如果干" => "如果幹", -"如法泡制" => "如法泡製", -"妙药" => "妙藥", -"始于" => "始於", -"委罪于人" => "委罪於人", -"委讬" => "委託", -"姜丝" => "姜絲", -"奸夫" => "姦夫", -"奸妇" => "姦婦", -"奸情" => "姦情", -"奸杀" => "姦殺", -"奸污" => "姦汙", -"奸淫" => "姦淫", -"奸邪" => "姦邪", -"威棱" => "威稜", -"婚后" => "婚後", -"婢仆" => "婢僕", -"嫁于" => "嫁於", -"嫁祸于人" => "嫁禍於人", -"嫌好道丑" => "嫌好道醜", -"娴于" => "嫻於", -"嬉游" => "嬉遊", -"嬴余" => "嬴餘", -"子之丰兮" => "子之丰兮", -"字汇" => "字彙", -"字里行间" => "字裡行間", -"存十一于千百" => "存十一於千百", -"存折" => "存摺", -"季后赛" => "季後賽", -"孤寡不谷" => "孤寡不穀", -"宇宙志" => "宇宙誌", -"安于" => "安於", -"安沈铁路" => "安瀋鐵路", -"安眠药" => "安眠藥", -"安胎药" => "安胎藥", -"完工后" => "完工後", -"完成后" => "完成後", -"宗周钟" => "宗周鐘", -"官地为采" => "官地為寀", -"官历" => "官曆", -"官庄" => "官莊", -"定于" => "定於", -"定准" => "定準", -"定制" => "定製", -"定都于" => "定都於", -"宜于" => "宜於", -"宦游" => "宦遊", -"宫里" => "宮裡", -"害于" => "害於", -"宴游" => "宴遊", -"家仆" => "家僕", -"家庄" => "家莊", -"家里" => "家裡", -"家丑" => "家醜", -"容后说明" => "容後說明", -"容于" => "容於", -"容范" => "容範", -"寄于" => "寄於", -"寄讬" => "寄託", -"寇雠" => "寇讎", -"富于" => "富於", -"富余" => "富餘", -"寒栗" => "寒慄", -"寒于" => "寒於", -"寓兵于农" => "寓兵於農", -"寓教于乐" => "寓教於樂", -"寓于" => "寓於", -"寡欲" => "寡慾", -"实干" => "實幹", -"写字台" => "寫字檯", -"宽于" => "寬於", -"宽余" => "寬餘", -"宽松" => "寬鬆", -"寮采" => "寮寀", -"宝山庄" => "寶山庄", -"宝历" => "寶曆", -"封面里" => "封面裡", -"射雕" => "射鵰", -"将于" => "將於", -"专美于前" => "專美於前", -"专注" => "專註", -"对折" => "對摺", -"对于" => "對於", -"对准" => "對準", -"对华发动" => "對華發動", -"对表" => "對錶", -"导游" => "導遊", -"小仆" => "小僕", -"小伙子" => "小夥子", -"小于" => "小於", -"小米面" => "小米麵", -"小只" => "小隻", -"少采" => "少採", -"少于" => "少於", -"就于" => "就於", -"就范" => "就範", -"就读于" => "就讀於", -"尸魂界" => "尸魂界", -"尼克松" => "尼克鬆", -"局里" => "局裡", -"居于" => "居於", -"屈服于" => "屈服於", -"屋子里" => "屋子裡", -"屋梁" => "屋樑", -"屋里" => "屋裡", -"屑于" => "屑於", -"屡顾尔仆" => "屢顧爾僕", -"属意于" => "屬意於", -"属于" => "屬於", -"屯扎" => "屯紮", -"屯里" => "屯裡", -"山崩钟应" => "山崩鐘應", -"山岳" => "山嶽", -"山后" => "山後", -"山梁" => "山樑", -"山庄" => "山莊", -"山药" => "山藥", -"山里" => "山裡", -"峰回" => "峰迴", -"昆剧" => "崑劇", -"昆山" => "崑山", -"昆仑" => "崑崙", -"昆曲" => "崑曲", -"昆腔" => "崑腔", -"昆苏" => "崑蘇", -"昆调" => "崑調", -"仑背" => "崙背", -"嶒棱" => "嶒稜", -"岳麓山" => "嶽麓山", -"川谷" => "川穀", -"巡回" => "巡迴", -"巡游" => "巡遊", -"工于" => "工於", -"工致" => "工緻", -"左后" => "左後", -"左冲右突" => "左衝右突", -"巧妇做不得无面馎饦" => "巧婦做不得無麵餺飥", -"巧干" => "巧幹", -"巧历" => "巧曆", -"差于" => "差於", -"已于" => "已於", -"巴尔干" => "巴爾幹", -"巷里" => "巷裡", -"市里" => "市裡", -"布谷" => "布穀", -"希伯来历" => "希伯來曆", -"帘子" => "帘子", -"帘布" => "帘布", -"师范" => "師範", -"席卷" => "席捲", -"带团参加" => "帶團參加", -"带发修行" => "帶髮修行", -"幕后" => "幕後", -"帮佣" => "幫傭", -"干系" => "干係", -"干着急" => "干著急", -"平平当当" => "平平當當", -"平准" => "平準", -"年后" => "年後", -"年历" => "年曆", -"年历史" => "年歷史", -"年谷" => "年穀", -"年里" => "年裡", -"并州" => "并州", -"干上" => "幹上", -"干下去" => "幹下去", -"干了" => "幹了", -"干事" => "幹事", -"干些" => "幹些", -"干人" => "幹人", -"干什么" => "幹什麼", -"干个" => "幹個", -"干劲" => "幹勁", -"干吏" => "幹吏", -"干员" => "幹員", -"干吗" => "幹嗎", -"干嘛" => "幹嘛", -"干坏事" => "幹壞事", -"干完" => "幹完", -"干家" => "幹家", -"干得" => "幹得", -"干性油" => "幹性油", -"干才" => "幹才", -"干掉" => "幹掉", -"干探" => "幹探", -"干校" => "幹校", -"干活" => "幹活", -"干流" => "幹流", -"干济" => "幹濟", -"干营生" => "幹營生", -"干父之蛊" => "幹父之蠱", -"干球温度" => "幹球溫度", -"干当" => "幹當", -"干的停当" => "幹的停當", -"干细胞" => "幹細胞", -"干线" => "幹線", -"干练" => "幹練", -"干缺" => "幹缺", -"干蛊" => "幹蠱", -"干警" => "幹警", -"干起来" => "幹起來", -"干路" => "幹路", -"干道" => "幹道", -"干部" => "幹部", -"干革命" => "幹革命", -"干头" => "幹頭", -"干么" => "幹麼", -"几划" => "幾劃", -"几天后" => "幾天後", -"几于" => "幾於", -"几丝" => "幾絲", -"几只" => "幾隻", -"几出" => "幾齣", -"广部" => "广部", -"府干卿" => "府干卿", -"府干扰" => "府干擾", -"府干政" => "府干政", -"府干涉" => "府干涉", -"府干犯" => "府干犯", -"府干预" => "府干預", -"府干" => "府幹", -"府后" => "府後", -"座钟" => "座鐘", -"康采恩" => "康採恩", -"康庄" => "康莊", -"厨余" => "廚餘", -"庙里" => "廟裡", -"广舍" => "廣捨", -"延后" => "延後", -"建于" => "建於", -"建都于" => "建都於", -"弄干" => "弄乾", -"弄丑" => "弄醜", -"弄脏" => "弄髒", -"弄松" => "弄鬆", -"吊儿郎当" => "弔兒郎當", -"吊卷" => "弔卷", -"吊古" => "弔古", -"吊唁" => "弔唁", -"吊丧" => "弔喪", -"吊孝" => "弔孝", -"吊客" => "弔客", -"吊带" => "弔帶", -"吊慰" => "弔慰", -"吊挂" => "弔掛", -"吊文" => "弔文", -"吊死" => "弔死", -"吊民伐罪" => "弔民伐罪", -"吊祭" => "弔祭", -"弘历" => "弘曆", -"弱于" => "弱於", -"弱硷" => "弱鹼", -"张三丰" => "張三丰", -"强占" => "強佔", -"强奸" => "強姦", -"强干" => "強幹", -"强于" => "強於", -"强硷" => "強鹼", -"别口气" => "彆口氣", -"别强" => "彆強", -"别扭" => "彆扭", -"别拗" => "彆拗", -"别气" => "彆氣", -"别着" => "彆著", -"弹子台" => "彈子檯", -"弹药" => "彈藥", -"汇报" => "彙報", -"汇整" => "彙整", -"汇编" => "彙編", -"汇纂" => "彙纂", -"汇辑" => "彙輯", -"汇集" => "彙集", -"形单影只" => "形單影隻", -"形于" => "形於", -"役于" => "役於", -"役于外物" => "役於外物", -"往来于" => "往來於", -"往后" => "往後", -"往里" => "往裡", -"往复" => "往複", -"很干" => "很乾", -"律历志" => "律曆志", -"后上" => "後上", -"后下" => "後下", -"后世" => "後世", -"后主" => "後主", -"后事" => "後事", -"后人" => "後人", -"后代" => "後代", -"后仰" => "後仰", -"后件" => "後件", -"后任" => "後任", -"后作" => "後作", -"后来" => "後來", -"后偏" => "後偏", -"后备" => "後備", -"后传" => "後傳", -"后分" => "後分", -"后到" => "後到", -"后力不继" => "後力不繼", -"后劲" => "後勁", -"后勤" => "後勤", -"后区" => "後區", -"后半" => "後半", -"后印" => "後印", -"后去" => "後去", -"后台" => "後台", -"后向" => "後向", -"后周" => "後周", -"后唐" => "後唐", -"后嗣" => "後嗣", -"后园" => "後園", -"后图" => "後圖", -"后土" => "後土", -"后埔" => "後埔", -"后堂" => "後堂", -"后尘" => "後塵", -"后壁" => "後壁", -"后天" => "後天", -"后奏" => "後奏", -"后娘" => "後娘", -"后学" => "後學", -"后宫" => "後宮", -"后山" => "後山", -"后巷" => "後巷", -"后市" => "後市", -"后年" => "後年", -"后几" => "後幾", -"后庄" => "後庄", -"后序" => "後序", -"后座" => "後座", -"后悔" => "後悔", -"后患" => "後患", -"后房" => "後房", -"后手" => "後手", -"后排" => "後排", -"后掠角" => "後掠角", -"后接" => "後接", -"后援" => "後援", -"后撤" => "後撤", -"后攻" => "後攻", -"后放" => "後放", -"后效" => "後效", -"后文" => "後文", -"后方" => "後方", -"后于" => "後於", -"后日" => "後日", -"后晋" => "後晉", -"后晌" => "後晌", -"后晚" => "後晚", -"后景" => "後景", -"后会" => "後會", -"后有" => "後有", -"后望镜" => "後望鏡", -"后期" => "後期", -"后果" => "後果", -"后桅" => "後桅", -"后梁" => "後梁", -"后桥" => "後橋", -"后步" => "後步", -"后段" => "後段", -"后殿" => "後殿", -"后母" => "後母", -"后派" => "後派", -"后浪" => "後浪", -"后凉" => "後涼", -"后港" => "後港", -"后汉" => "後漢", -"后为" => "後為", -"后无来者" => "後無來者", -"后燕" => "後燕", -"后生" => "後生", -"后用" => "後用", -"后由" => "後由", -"后盾" => "後盾", -"后知" => "後知", -"后福" => "後福", -"后秃" => "後禿", -"后秦" => "後秦", -"后空翻" => "後空翻", -"后窗" => "後窗", -"后站" => "後站", -"后端" => "後端", -"后竹围" => "後竹圍", -"后节" => "後節", -"后篇" => "後篇", -"后继" => "後繼", -"后续" => "後續", -"后置" => "後置", -"后者" => "後者", -"后肢" => "後肢", -"后背" => "後背", -"后脑" => "後腦", -"后脚" => "後腳", -"后腿" => "後腿", -"后膛" => "後膛", -"后花园" => "後花園", -"后菜园" => "後菜園", -"后叶" => "後葉", -"后行" => "後行", -"后街" => "後街", -"后卫" => "後衛", -"后裔" => "後裔", -"后䙓" => "後襬", -"后视镜" => "後視鏡", -"后计" => "後計", -"后记" => "後記", -"后设" => "後設", -"后读" => "後讀", -"后走" => "後走", -"后起" => "後起", -"后赵" => "後趙", -"后足" => "後足", -"后跟" => "後跟", -"后路" => "後路", -"后身" => "後身", -"后车" => "後車", -"后辈" => "後輩", -"后轮" => "後輪", -"后转" => "後轉", -"后述" => "後述", -"后退" => "後退", -"后送" => "後送", -"后进" => "後進", -"后过" => "後過", -"后遗症" => "後遺症", -"后边" => "後邊", -"后部" => "後部", -"后镜" => "後鏡", -"后门" => "後門", -"后防" => "後防", -"后院" => "後院", -"后集" => "後集", -"后面" => "後面", -"后项" => "後項", -"后头" => "後頭", -"后颈" => "後頸", -"后顾" => "後顧", -"后魏" => "後魏", -"后点" => "後點", -"后龙" => "後龍", -"徐干" => "徐幹", -"徒讬空言" => "徒託空言", -"得于" => "得於", -"徜徉于" => "徜徉於", -"从事于" => "從事於", -"从于" => "從於", -"从里到外" => "從裡到外", -"从里向外" => "從裡向外", -"复始" => "復始", -"复雠" => "復讎", -"征信" => "徵信", -"征候" => "徵候", -"征兆" => "徵兆", -"征兵" => "徵兵", -"征到" => "徵到", -"征募" => "徵募", -"征友" => "徵友", -"征召" => "徵召", -"征引" => "徵引", -"征得" => "徵得", -"征收" => "徵收", -"征文" => "徵文", -"征求" => "徵求", -"征状" => "徵狀", -"征用" => "徵用", -"征税" => "徵稅", -"征稿" => "徵稿", -"征结" => "徵結", -"征聘" => "徵聘", -"征训" => "徵訓", -"征询" => "徵詢", -"征调" => "徵調", -"征象" => "徵象", -"征购" => "徵購", -"征集" => "徵集", -"征验出" => "徵驗出", -"心愿" => "心愿", -"心于" => "心於", -"心细如发" => "心細如髮", -"心荡神驰" => "心蕩神馳", -"心药" => "心藥", -"心里" => "心裡", -"心余" => "心餘", -"志于" => "志於", -"忙并" => "忙併", -"忙于" => "忙於", -"忙里" => "忙裡", -"忠仆" => "忠僕", -"忠于" => "忠於", -"快干" => "快乾", -"快干" => "快幹", -"快快当当" => "快快當當", -"快冲" => "快衝", -"忽前忽后" => "忽前忽後", -"怎么" => "怎麼", -"怎么着" => "怎麼著", -"怒形于色" => "怒形於色", -"怒于" => "怒於", -"怒发冲冠" => "怒髮衝冠", -"思前思后" => "思前思後", -"思前想后" => "思前想後", -"急于" => "急於", -"急冲而下" => "急衝而下", -"性征" => "性徵", -"性欲" => "性慾", -"怪里怪气" => "怪裡怪氣", -"怫郁" => "怫鬱", -"息谷" => "息穀", -"恰才" => "恰纔", -"悍药" => "悍藥", -"悒郁" => "悒鬱", -"悒郁寡欢" => "悒鬱寡歡", -"悠游" => "悠遊", -"闷着头儿干" => "悶著頭兒幹", -"悸栗" => "悸慄", -"情欲" => "情慾", -"惇朴" => "惇樸", -"恶直丑正" => "惡直醜正", -"惴栗" => "惴慄", -"意大利面" => "意大利麵", -"意面" => "意麵", -"爱困" => "愛睏", -"感冒药" => "感冒藥", -"感于" => "感於", -"愧于" => "愧於", -"愿朴" => "愿樸", -"愿而恭" => "愿而恭", -"慌里慌张" => "慌裡慌張", -"惯于" => "慣於", -"慰藉" => "慰藉", -"庆吊" => "慶弔", -"庆历" => "慶曆", -"欲令智昏" => "慾令智昏", -"欲壑难填" => "慾壑難填", -"欲念" => "慾念", -"欲望" => "慾望", -"欲海" => "慾海", -"欲火" => "慾火", -"欲障" => "慾障", -"忧形于色" => "憂形於色", -"忧郁" => "憂鬱", -"凭吊" => "憑弔", -"凭藉着" => "憑藉著", -"恳讬" => "懇託", -"懈松" => "懈鬆", -"应征" => "應徵", -"应钟" => "應鐘", -"蒙懂" => "懞懂", -"蒙蒙懂懂" => "懞懞懂懂", -"蒙直" => "懞直", -"惩前毖后" => "懲前毖後", -"懒于" => "懶於", -"怀里" => "懷裡", -"怀表" => "懷錶", -"悬梁" => "懸樑", -"悬臂梁" => "懸臂樑", -"悬钟" => "懸鐘", -"惧于" => "懼於", -"懿范" => "懿範", -"恋恋不舍" => "戀戀不捨", -"成于" => "成於", -"成药" => "成藥", -"或于" => "或於", -"戬谷" => "戩穀", -"截发" => "截髮", -"战天斗地" => "戰天鬥地", -"战后" => "戰後", -"战栗" => "戰慄", -"战斗" => "戰鬥", -"戴表" => "戴錶", -"房里" => "房裡", -"扁拟谷盗虫" => "扁擬穀盜蟲", -"手冢治虫" => "手塚治虫", -"手折" => "手摺", -"手里" => "手裡", -"手表" => "手錶", -"手松" => "手鬆", -"才干" => "才幹", -"打干哕" => "打乾噦", -"打并" => "打併", -"打卡钟" => "打卡鐘", -"打干" => "打幹", -"打拼" => "打拚", -"打谷" => "打穀", -"打钟" => "打鐘", -"打斗" => "打鬥", -"扞御" => "扞禦", -"扯面" => "扯麵", -"批准的" => "批准的", -"批复" => "批複", -"批注" => "批註", -"批斗" => "批鬥", -"承先启后" => "承先啟後", -"承制" => "承製", -"抑郁" => "抑鬱", -"抓奸" => "抓姦", -"抓药" => "抓藥", -"抓斗" => "抓鬥", -"投药" => "投藥", -"抗癌药" => "抗癌藥", -"抗御" => "抗禦", -"抗药" => "抗藥", -"抗硷" => "抗鹼", -"折冲" => "折衝", -"披榛采兰" => "披榛採蘭", -"披头散发" => "披頭散髮", -"披发" => "披髮", -"抱朴而长吟兮" => "抱朴而長吟兮", -"抱素怀朴" => "抱素懷樸", -"抵御" => "抵禦", -"抹干" => "抹乾", -"抽公签" => "抽公籤", -"抽签" => "抽籤", -"抿发" => "抿髮", -"拆伙" => "拆夥", -"拈须" => "拈鬚", -"拉纤" => "拉縴", -"拉面" => "拉麵", -"拒人于" => "拒人於", -"拒于" => "拒於", -"拓朴" => "拓樸", -"拗别" => "拗彆", -"拘于" => "拘於", -"拘泥于" => "拘泥於", -"拙于" => "拙於", -"拙朴" => "拙樸", -"拼命" => "拚命", -"拼舍" => "拚捨", -"拼死" => "拚死", -"拼斗" => "拚鬥", -"拜讬" => "拜託", -"括发" => "括髮", -"拭干" => "拭乾", -"拮据" => "拮据", -"拿准" => "拿準", -"拿破仑" => "拿破崙", -"指手划脚" => "指手劃腳", -"振荡" => "振蕩", -"捆扎" => "捆紮", -"捉奸" => "捉姦", -"捉发" => "捉髮", -"捍御" => "捍禦", -"捏面人" => "捏麵人", -"舍不得" => "捨不得", -"舍出" => "捨出", -"舍去" => "捨去", -"舍命" => "捨命", -"舍堕" => "捨墮", -"舍安就危" => "捨安就危", -"舍实" => "捨實", -"舍己从人" => "捨己從人", -"舍己救人" => "捨己救人", -"舍己为人" => "捨己為人", -"舍己为公" => "捨己為公", -"舍己为国" => "捨己為國", -"舍得" => "捨得", -"舍我其谁" => "捨我其誰", -"舍本逐末" => "捨本逐末", -"舍弃" => "捨棄", -"舍死忘生" => "捨死忘生", -"舍生" => "捨生", -"舍短取长" => "捨短取長", -"舍身" => "捨身", -"舍车保帅" => "捨車保帥", -"舍近求远" => "捨近求遠", -"卷住" => "捲住", -"卷来" => "捲來", -"卷儿" => "捲兒", -"卷入" => "捲入", -"卷动" => "捲動", -"卷去" => "捲去", -"卷图" => "捲圖", -"卷土重来" => "捲土重來", -"卷尺" => "捲尺", -"卷心菜" => "捲心菜", -"卷成" => "捲成", -"卷曲" => "捲曲", -"卷款逃走" => "捲款逃走", -"卷毛" => "捲毛", -"卷烟" => "捲煙", -"卷筒" => "捲筒", -"卷帘" => "捲簾", -"卷纸" => "捲紙", -"卷缩" => "捲縮", -"卷舌" => "捲舌", -"卷菸" => "捲菸", -"卷袖" => "捲袖", -"卷走" => "捲走", -"卷起" => "捲起", -"卷轴" => "捲軸", -"卷逃" => "捲逃", -"卷铺盖" => "捲鋪蓋", -"卷云" => "捲雲", -"卷风" => "捲風", -"卷发" => "捲髮", -"捵面" => "捵麵", -"扫荡" => "掃蕩", -"掌柜" => "掌柜", -"排骨面" => "排骨麵", -"挂帘" => "掛帘", -"挂钟" => "掛鐘", -"挂面" => "掛麵", -"采下" => "採下", -"采伐" => "採伐", -"采住" => "採住", -"采信" => "採信", -"采光" => "採光", -"采到" => "採到", -"采制" => "採制", -"采区" => "採區", -"采去" => "採去", -"采取" => "採取", -"采回" => "採回", -"采在" => "採在", -"采好" => "採好", -"采得" => "採得", -"采拾" => "採拾", -"采挖" => "採挖", -"采掘" => "採掘", -"采摘" => "採摘", -"采摭" => "採摭", -"采择" => "採擇", -"采撷" => "採擷", -"采收" => "採收", -"采料" => "採料", -"采暖" => "採暖", -"采桑" => "採桑", -"采样" => "採樣", -"采樵人" => "採樵人", -"采树种" => "採樹種", -"采气" => "採氣", -"采油" => "採油", -"采为" => "採為", -"采煤" => "採煤", -"采猎" => "採獵", -"采珠" => "採珠", -"采生折割" => "採生折割", -"采用" => "採用", -"采的" => "採的", -"采石" => "採石", -"采石场" => "採石場", -"采石厂" => "採石廠", -"采砂场" => "採砂場", -"采矿" => "採礦", -"采种" => "採種", -"采空区" => "採空區", -"采空采穗" => "採空採穗", -"采纳" => "採納", -"采给" => "採給", -"采花" => "採花", -"采芹人" => "採芹人", -"采茶" => "採茶", -"采菊" => "採菊", -"采莲" => "採蓮", -"采薇" => "採薇", -"采药" => "採藥", -"采行" => "採行", -"采补" => "採補", -"采访" => "採訪", -"采证" => "採證", -"采买" => "採買", -"采购" => "採購", -"采办" => "採辦", -"采运" => "採運", -"采过" => "採過", -"采选" => "採選", -"采金" => "採金", -"采录" => "採錄", -"采铁" => "採鐵", -"采集" => "採集", -"采风" => "採風", -"采风问俗" => "採風問俗", -"采食" => "採食", -"采盐" => "採鹽", -"掣签" => "掣籤", -"接着说" => "接著說", -"推讬" => "推託", -"提子干" => "提子乾", -"提心吊胆" => "提心弔膽", -"提摩太后书" => "提摩太後書", -"插于" => "插於", -"插足于" => "插足於", -"换签" => "換籤", -"换药" => "換藥", -"换只" => "換隻", -"换发" => "換髮", -"揩干" => "揩乾", -"揪采" => "揪採", -"揭丑" => "揭醜", -"挥手表" => "揮手表", -"搋面" => "搋麵", -"损于" => "損於", -"搏斗" => "搏鬥", -"摇荡" => "搖蕩", -"搭干铺" => "搭乾鋪", -"搭伙" => "搭夥", -"抢占" => "搶佔", -"搽药" => "搽藥", -"摧坚获丑" => "摧堅獲醜", -"摭采" => "摭採", -"摸棱" => "摸稜", -"折合" => "摺合", -"折奏" => "摺奏", -"折子" => "摺子", -"折尺" => "摺尺", -"折扇" => "摺扇", -"折梯" => "摺梯", -"折椅" => "摺椅", -"折叠" => "摺疊", -"折痕" => "摺痕", -"折篷" => "摺篷", -"折纸" => "摺紙", -"折裙" => "摺裙", -"捞干" => "撈乾", -"捞面" => "撈麵", -"撚须" => "撚鬚", -"撞木钟" => "撞木鐘", -"撞球台" => "撞球檯", -"撞钟" => "撞鐘", -"撞阵冲军" => "撞陣衝軍", -"撤并" => "撤併", -"撤后" => "撤後", -"拨谷" => "撥穀", -"播于" => "播於", -"扑冬" => "撲鼕", -"擀面" => "擀麵", -"擅于" => "擅於", -"击钟" => "擊鐘", -"担仔面" => "擔仔麵", -"担担面" => "擔擔麵", -"担着" => "擔著", -"担负着" => "擔負著", -"据云" => "據云", -"据干而窥井底" => "據榦而窺井底", -"挤身于" => "擠身於", -"擢发" => "擢髮", -"擢发难数" => "擢髮難數", -"擦干" => "擦乾", -"擦药" => "擦藥", -"拟于" => "擬於", -"拧干" => "擰乾", -"摆钟" => "擺鐘", -"摄于" => "攝於", -"摄制" => "攝製", -"支干" => "支幹", -"收获" => "收穫", -"改征" => "改徵", -"改于" => "改於", -"攻占" => "攻佔", -"放蒙挣" => "放懞掙", -"放荡" => "放蕩", -"放松" => "放鬆", -"故于" => "故於", -"敏于" => "敏於", -"败于" => "敗於", -"叙说着" => "敘說著", -"教于" => "教於", -"敢干" => "敢幹", -"敢于" => "敢於", -"散伙" => "散夥", -"散于" => "散於", -"散荡" => "散蕩", -"敦朴" => "敦樸", -"敬挽" => "敬輓", -"敲钟" => "敲鐘", -"整只" => "整隻", -"敌后" => "敵後", -"敷药" => "敷藥", -"数天后" => "數天後", -"数罪并罚" => "數罪併罰", -"数与虏确" => "數與虜确", -"文汇报" => "文匯報", -"文思泉涌" => "文思泉湧", -"斗转参横" => "斗轉參橫", -"斗哄" => "斗鬨", -"料斗" => "料鬥", -"斫雕为朴" => "斫雕為樸", -"新历" => "新曆", -"新历史" => "新歷史", -"新扎" => "新紮", -"新庄" => "新莊", -"斲雕为朴" => "斲雕為樸", -"断后" => "斷後", -"断发" => "斷髮", -"方便面" => "方便麵", -"方几" => "方几", -"于一役" => "於一役", -"于七" => "於七", -"于世" => "於世", -"于事" => "於事", -"于事无补" => "於事無補", -"于人" => "於人", -"于今" => "於今", -"于他" => "於他", -"于伏" => "於伏", -"于何" => "於何", -"于你" => "於你", -"于前" => "於前", -"于劣" => "於劣", -"于勤" => "於勤", -"于呼哀哉" => "於呼哀哉", -"于国" => "於國", -"于坏" => "於坏", -"于垂" => "於垂", -"于夫罗" => "於夫羅", -"于她" => "於她", -"于好" => "於好", -"于始" => "於始", -"于它" => "於它", -"于家" => "於家", -"于密" => "於密", -"于左" => "於左", -"于差" => "於差", -"于己" => "於己", -"于市" => "於市", -"于幕" => "於幕", -"于幼华" => "於幼華", -"于弱" => "於弱", -"于强" => "於強", -"于征" => "於征", -"于后" => "於後", -"于心" => "於心", -"于心何忍" => "於心何忍", -"于思" => "於思", -"于怀" => "於懷", -"于我" => "於我", -"于斯" => "於斯", -"于于" => "於於", -"于是" => "於是", -"于时" => "於時", -"于梨华" => "於梨華", -"于乐" => "於樂", -"于此" => "於此", -"于民" => "於民", -"于法" => "於法", -"于法无据" => "於法無據", -"于潜县" => "於潛縣", -"于火" => "於火", -"于焉" => "於焉", -"于墙" => "於牆", -"于物" => "於物", -"于田" => "於田", -"于毕" => "於畢", -"于尽" => "於盡", -"于盲" => "於盲", -"于祂" => "於祂", -"于穆" => "於穆", -"于终" => "於終", -"于美" => "於美", -"于色" => "於色", -"于行" => "於行", -"于衷" => "於衷", -"于该" => "於該", -"于农" => "於農", -"于途" => "於途", -"于丑" => "於醜", -"于野" => "於野", -"于陆" => "於陸", -"于飞" => "於飛", -"施舍" => "施捨", -"施于" => "施於", -"施舍之道" => "施舍之道", -"施药" => "施藥", -"旁征博引" => "旁徵博引", -"旁注" => "旁註", -"旅游" => "旅遊", -"旋干转坤" => "旋乾轉坤", -"旋绕着" => "旋繞著", -"旋回" => "旋迴", -"族里" => "族裡", -"日后" => "日後", -"日历" => "日曆", -"日志" => "日誌", -"早于" => "早於", -"旱干" => "旱乾", -"昆仑" => "昆崙", -"升平" => "昇平", -"升阳" => "昇陽", -"明征" => "明徵", -"明于" => "明於", -"明窗净几" => "明窗淨几", -"明范" => "明範", -"明里" => "明裡", -"易于" => "易於", -"星历" => "星曆", -"星辰表" => "星辰錶", -"星斗" => "星鬥", -"春假里" => "春假裡", -"春天里" => "春天裡", -"春药" => "春藥", -"春游" => "春遊", -"昧于" => "昧於", -"时钟" => "時鐘", -"晃荡" => "晃蕩", -"晋升" => "晉陞", -"晒干" => "晒乾", -"晞发" => "晞髮", -"晨钟" => "晨鐘", -"晨钟暮鼓" => "晨鐘暮鼓", -"景致" => "景緻", -"晾干" => "晾乾", -"晕船药" => "暈船藥", -"晕车药" => "暈車藥", -"暗地里" => "暗地裡", -"暗沟里" => "暗溝裡", -"暗里" => "暗裡", -"暗斗" => "暗鬥", -"畅游" => "暢遊", -"暂于" => "暫於", -"暮鼓晨钟" => "暮鼓晨鐘", -"历元" => "曆元", -"历命" => "曆命", -"历始" => "曆始", -"历室" => "曆室", -"历尾" => "曆尾", -"历数" => "曆數", -"历日" => "曆日", -"历书" => "曆書", -"历本" => "曆本", -"历法" => "曆法", -"历纪" => "曆紀", -"晒干" => "曬乾", -"晒谷" => "曬穀", -"更仆难数" => "更僕難數", -"更签" => "更籤", -"书后" => "書後", -"书呆子" => "書獃子", -"书签" => "書籤", -"曾于" => "曾於", -"曾朴" => "曾樸", -"最后" => "最後", -"会占" => "會佔", -"会干" => "會幹", -"会后" => "會後", -"会于" => "會於", -"会里" => "會裡", -"月历" => "月曆", -"有仆" => "有僕", -"有助于" => "有助於", -"有害于" => "有害於", -"有损于" => "有損於", -"有求于人" => "有求於人", -"有奖征答" => "有獎徵答", -"有益于" => "有益於", -"有棱有角" => "有稜有角", -"有只" => "有隻", -"有余" => "有餘", -"有发头陀寺" => "有髮頭陀寺", -"服务于" => "服務於", -"服从于" => "服從於", -"服于" => "服於", -"服药" => "服藥", -"朝干夕惕" => "朝乾夕惕", -"朝后" => "朝後", -"朝钟" => "朝鐘", -"朦胧" => "朦朧", -"木偶戏扎" => "木偶戲紮", -"木制" => "木製", -"未干" => "未乾", -"末药" => "末藥", -"术赤" => "朮赤", -"朱庆余" => "朱慶餘", -"朱理安历" => "朱理安曆", -"李连杰" => "李連杰", -"村庄" => "村莊", -"村落发" => "村落發", -"村里" => "村裡", -"束发" => "束髮", -"杯干" => "杯乾", -"杰特" => "杰特", -"东岳" => "東嶽", -"东冲西突" => "東衝西突", -"东游" => "東遊", -"松山庄" => "松山庄", -"松柏后凋" => "松柏後凋", -"板着脸" => "板著臉", -"板荡" => "板蕩", -"枕经藉史" => "枕經藉史", -"枕藉" => "枕藉", -"林宏岳" => "林宏嶽", -"林郁方" => "林郁方", -"林钟" => "林鐘", -"果干" => "果乾", -"果子干" => "果子乾", -"果于" => "果於", -"枝不得大于干" => "枝不得大於榦", -"枝干" => "枝幹", -"枯干" => "枯乾", -"某只" => "某隻", -"染指于" => "染指於", -"染发" => "染髮", -"柜上" => "柜上", -"柜台" => "柜台", -"柜子" => "柜子", -"柱梁" => "柱樑", -"校准" => "校準", -"校雠学" => "校讎學", -"核准的" => "核准的", -"格于" => "格於", -"格范" => "格範", -"格斗" => "格鬥", -"桂圆干" => "桂圓乾", -"案发后" => "案發後", -"桌历" => "桌曆", -"桑干" => "桑乾", -"条干" => "條幹", -"梨干" => "梨乾", -"械斗" => "械鬥", -"弃舍" => "棄捨", -"棉制" => "棉製", -"棒子面" => "棒子麵", -"枣庄" => "棗莊", -"栋梁" => "棟樑", -"棫朴" => "棫樸", -"栖于" => "棲於", -"植发" => "植髮", -"椰枣干" => "椰棗乾", -"楚庄王" => "楚莊王", -"桢干" => "楨幹", -"业余" => "業餘", -"榨干" => "榨乾", -"乐意于" => "樂意於", -"乐于" => "樂於", -"樊于期" => "樊於期", -"梁上" => "樑上", -"梁子" => "樑子", -"梁书" => "樑書", -"梁柱" => "樑柱", -"标志着" => "標志著", -"标标致致" => "標標致致", -"标准" => "標準", -"标签" => "標籤", -"标致" => "標緻", -"标志" => "標誌", -"模棱" => "模稜", -"模范" => "模範", -"模范棒棒堂" => "模范棒棒堂", -"模制" => "模製", -"样范" => "樣範", -"樵采" => "樵採", -"朴修斯" => "樸修斯", -"朴厚" => "樸厚", -"朴学" => "樸學", -"朴实" => "樸實", -"朴念仁" => "樸念仁", -"朴拙" => "樸拙", -"朴樕" => "樸樕", -"朴父" => "樸父", -"朴直" => "樸直", -"朴素" => "樸素", -"朴讷" => "樸訥", -"朴质" => "樸質", -"朴鄙" => "樸鄙", -"朴重" => "樸重", -"朴野" => "樸野", -"朴野无文" => "樸野無文", -"朴钝" => "樸鈍", -"朴陋" => "樸陋", -"朴马" => "樸馬", -"朴鲁" => "樸魯", -"树干" => "樹幹", -"树干" => "樹榦", -"树梁" => "樹樑", -"桥梁" => "橋樑", -"机绣" => "機繡", -"横征暴敛" => "橫徵暴斂", -"横梁" => "橫樑", -"横冲" => "橫衝", -"台子" => "檯子", -"台布" => "檯布", -"台灯" => "檯燈", -"台球" => "檯球", -"台面" => "檯面", -"柜台" => "櫃檯", -"栉发工" => "櫛髮工", -"次于" => "次於", -"欺蒙" => "欺矇", -"歇后" => "歇後", -"歌钟" => "歌鐘", -"欧游" => "歐遊", -"止咳药" => "止咳藥", -"止于" => "止於", -"止痛药" => "止痛藥", -"止血药" => "止血藥", -"正官庄" => "正官庄", -"正后" => "正後", -"正于" => "正於", -"正当着" => "正當著", -"此后" => "此後", -"武斗" => "武鬥", -"归并" => "歸併", -"归功于" => "歸功於", -"归咎于" => "歸咎於", -"归因于" => "歸因於", -"归于" => "歸於", -"归罪于" => "歸罪於", -"归随于" => "歸隨於", -"归顺于" => "歸順於", -"归余" => "歸餘", -"死伤相藉" => "死傷相藉", -"死后" => "死後", -"死于" => "死於", -"死里求生" => "死裡求生", -"死里逃生" => "死裡逃生", -"殖谷" => "殖穀", -"残余" => "殘餘", -"杀虫药" => "殺虫藥", -"杀虫药" => "殺蟲藥", -"壳里" => "殼裡", -"殿后" => "殿後", -"殿钟自鸣" => "殿鐘自鳴", -"毁于" => "毀於", -"毁钟为铎" => "毀鐘為鐸", -"母范" => "母範", -"每于" => "每於", -"每只" => "每隻", -"毒药" => "毒藥", -"比划" => "比劃", -"毛姜" => "毛薑", -"毛发" => "毛髮", -"毫厘" => "毫釐", -"毫发" => "毫髮", -"气郁" => "氣鬱", -"氤郁" => "氤鬱", -"氯霉素" => "氯黴素", -"水准" => "水準", -"水里" => "水裡", -"水里乡" => "水里鄉", -"水硷" => "水鹼", -"永历" => "永曆", -"求助于" => "求助於", -"求教于" => "求教於", -"求知欲" => "求知慾", -"求签" => "求籤", -"汗硷" => "汗鹼", -"污蔑" => "汙衊", -"池里" => "池裡", -"污蔑" => "污衊", -"汲于" => "汲於", -"汲汲于" => "汲汲於", -"决斗" => "決鬥", -"沈淀" => "沈澱", -"沈着" => "沈著", -"沈郁" => "沈鬱", -"沉湎于" => "沉湎於", -"沉溺于" => "沉溺於", -"沉淀" => "沉澱", -"沉迷于" => "沉迷於", -"沉醉于" => "沉醉於", -"沉郁" => "沉鬱", -"没干没净" => "沒乾沒淨", -"没事干" => "沒事幹", -"没干" => "沒幹", -"没梢干" => "沒梢幹", -"没样范" => "沒樣範", -"没药" => "沒藥", -"冲冠发怒" => "沖冠髮怒", -"冲着" => "沖著", -"沙里淘金" => "沙裡淘金", -"河流汇集" => "河流匯集", -"河里" => "河裡", -"油漆未干" => "油漆未乾", -"油面" => "油麵", -"泛游" => "泛遊", -"泡面" => "泡麵", -"波棱菜" => "波稜菜", -"波发藻" => "波髮藻", -"泥于" => "泥於", -"注云" => "注云", -"泱郁" => "泱鬱", -"泳气钟" => "泳氣鐘", -"洄游" => "洄遊", -"洋面" => "洋麵", -"洗手不干" => "洗手不幹", -"洗发" => "洗髮", -"洗发精" => "洗髮精", -"洛钟东应" => "洛鐘東應", -"洪范" => "洪範", -"洪钟" => "洪鐘", -"汹涌" => "洶湧", -"活动于" => "活動於", -"派团参加" => "派團參加", -"流于" => "流於", -"流荡" => "流蕩", -"流行于" => "流行於", -"浩荡" => "浩蕩", -"浪琴表" => "浪琴錶", -"浪荡" => "浪蕩", -"浮于" => "浮於", -"浮荡" => "浮蕩", -"浮游" => "浮遊", -"浮松" => "浮鬆", -"海干" => "海乾", -"浸于" => "浸於", -"涂着" => "涂著", -"涂谨申" => "涂謹申", -"消炎药" => "消炎藥", -"消肿药" => "消腫藥", -"涉足于" => "涉足於", -"液晶表" => "液晶錶", -"涳蒙" => "涳濛", -"涸干" => "涸乾", -"凉面" => "涼麵", -"淑范" => "淑範", -"泪干" => "淚乾", -"泪如泉涌" => "淚如泉湧", -"淡于" => "淡於", -"淡蒙蒙" => "淡濛濛", -"淡朱" => "淡硃", -"净余" => "淨餘", -"净发" => "淨髮", -"淫欲" => "淫慾", -"淫荡" => "淫蕩", -"深于" => "深於", -"淳朴" => "淳樸", -"港制" => "港製", -"游荡" => "游蕩", -"浑朴" => "渾樸", -"凑合着" => "湊合著", -"湖里" => "湖裡", -"湘绣" => "湘繡", -"湘累" => "湘纍", -"湟潦生苹" => "湟潦生苹", -"涌上" => "湧上", -"涌来" => "湧來", -"涌入" => "湧入", -"涌出" => "湧出", -"涌向" => "湧向", -"涌泉" => "湧泉", -"涌现" => "湧現", -"涌起" => "湧起", -"涌进" => "湧進", -"湮郁" => "湮鬱", -"汤下面" => "湯下麵", -"汤团" => "湯糰", -"汤药" => "湯藥", -"汤面" => "湯麵", -"源于" => "源於", -"准备" => "準備", -"准儿" => "準兒", -"准则" => "準則", -"准噶尔" => "準噶爾", -"准定" => "準定", -"准平原" => "準平原", -"准度" => "準度", -"准据" => "準據", -"准新娘" => "準新娘", -"准新郎" => "準新郎", -"准星" => "準星", -"准是" => "準是", -"准时" => "準時", -"准会" => "準會", -"准决赛" => "準決賽", -"准的" => "準的", -"准确" => "準確", -"准线" => "準線", -"准绳" => "準繩", -"准话" => "準話", -"准头" => "準頭", -"溟蒙" => "溟濛", -"溢于" => "溢於", -"溢于言表" => "溢於言表", -"溲面" => "溲麵", -"溶于" => "溶於", -"溺于" => "溺於", -"滃郁" => "滃鬱", -"滑藉" => "滑藉", -"汇丰" => "滙豐", -"卤味" => "滷味", -"卤水" => "滷水", -"卤汁" => "滷汁", -"卤湖" => "滷湖", -"卤肉" => "滷肉", -"卤菜" => "滷菜", -"卤蛋" => "滷蛋", -"卤制" => "滷製", -"卤鸡" => "滷雞", -"卤面" => "滷麵", -"满于" => "滿於", -"满满当当" => "滿滿當當", -"漂荡" => "漂蕩", -"沤郁" => "漚鬱", -"汉弥登钟" => "漢彌登鐘", -"漫游" => "漫遊", -"潜水钟" => "潛水鐘", -"潭里" => "潭裡", -"潮涌" => "潮湧", -"溃于" => "潰於", -"澄澹精致" => "澄澹精致", -"澒蒙" => "澒濛", -"泽渗漓而下降" => "澤滲灕而下降", -"淀粉" => "澱粉", -"澹台" => "澹臺", -"激于" => "激於", -"激荡" => "激蕩", -"浓于" => "濃於", -"浓发" => "濃髮", -"蒙汜" => "濛汜", -"蒙蒙细雨" => "濛濛細雨", -"蒙雾" => "濛霧", -"蒙松雨" => "濛鬆雨", -"泻药" => "瀉藥", -"沈吉线" => "瀋吉線", -"沈山线" => "瀋山線", -"沈州" => "瀋州", -"沈水" => "瀋水", -"沈河" => "瀋河", -"沈海" => "瀋海", -"沈海铁路" => "瀋海鐵路", -"沈阳" => "瀋陽", -"濒于" => "瀕於", -"弥山遍野" => "瀰山遍野", -"弥漫" => "瀰漫", -"弥漫着" => "瀰漫著", -"弥弥" => "瀰瀰", -"灌于" => "灌於", -"灌药" => "灌藥", -"漓湘" => "灕湘", -"漓然" => "灕然", -"火并" => "火併", -"火签" => "火籤", -"火药" => "火藥", -"灰蒙" => "灰濛", -"灰蒙蒙" => "灰濛濛", -"炆面" => "炆麵", -"炒面" => "炒麵", -"炮制" => "炮製", -"炸药" => "炸藥", -"炸酱面" => "炸醬麵", -"为准" => "為準", -"为着" => "為著", -"乌发" => "烏髮", -"乌龙面" => "烏龍麵", -"烘干" => "烘乾", -"烘制" => "烘製", -"烤干" => "烤乾", -"焙干" => "焙乾", -"无助于" => "無助於", -"无动于衷" => "無動於衷", -"无可救药" => "無可救藥", -"无后" => "無後", -"无损于" => "無損於", -"无梁楼盖" => "無樑樓蓋", -"无济于事" => "無濟於事", -"无畏于" => "無畏於", -"无补于事" => "無補於事", -"无视于" => "無視於", -"无余" => "無餘", -"然后" => "然後", -"然身死才数月耳" => "然身死纔數月耳", -"炼药" => "煉藥", -"炼制" => "煉製", -"煎药" => "煎藥", -"煎面" => "煎麵", -"烟卷" => "煙捲", -"烟斗丝" => "煙斗絲", -"烟斗" => "煙鬥", -"烟硷" => "煙鹼", -"照占" => "照佔", -"照入签" => "照入籤", -"照准" => "照準", -"煨干" => "煨乾", -"煮面" => "煮麵", -"熔于" => "熔於", -"熨斗" => "熨鬥", -"熬药" => "熬藥", -"热衷于" => "熱衷於", -"炖药" => "燉藥", -"燎发" => "燎髮", -"烧干" => "燒乾", -"烧硷" => "燒鹼", -"燕几" => "燕几", -"燕游" => "燕遊", -"烫发" => "燙髮", -"烫面" => "燙麵", -"营干" => "營幹", -"烬余" => "燼餘", -"争先恐后" => "爭先恐後", -"争奇斗艳" => "爭奇鬥艷", -"争奇斗豔" => "爭奇鬥豔", -"争妍斗胜" => "爭妍鬥勝", -"争妍斗豔" => "爭妍鬥豔", -"争斗" => "爭鬥", -"爰定祥历" => "爰定祥厤", -"爽荡" => "爽蕩", -"尔冬升" => "爾冬陞", -"尔后" => "爾後", -"片言只语" => "片言隻語", -"牙签" => "牙籤", -"牛肉面" => "牛肉麵", -"牛只" => "牛隻", -"物欲" => "物慾", -"特征" => "特徵", -"特效药" => "特效藥", -"特于" => "特於", -"特制" => "特製", -"牵一发" => "牽一髮", -"牵系" => "牽繫", -"荦确" => "犖确", -"狂并潮" => "狂併潮", -"狃于" => "狃於", -"狃于成见" => "狃於成見", -"狐藉虎威" => "狐藉虎威", -"狼藉" => "狼藉", -"猛于" => "猛於", -"猛冲" => "猛衝", -"猜三划五" => "猜三划五", -"奖杯" => "獎盃", -"独占" => "獨佔", -"独占鳌头" => "獨佔鰲頭", -"兽欲" => "獸慾", -"献丑" => "獻醜", -"率团参加" => "率團參加", -"玉历" => "玉曆", -"王庄" => "王莊", -"王余鱼" => "王餘魚", -"班里" => "班裡", -"理发" => "理髮", -"琴钟" => "琴鐘", -"瑶签" => "瑤籤", -"环游" => "環遊", -"甘于" => "甘於", -"甚于" => "甚於", -"甚么" => "甚麼", -"甜水面" => "甜水麵", -"甜面酱" => "甜麵醬", -"生力面" => "生力麵", -"生于" => "生於", -"生姜" => "生薑", -"生锈" => "生鏽", -"生发" => "生髮", -"产后" => "產後", -"用于" => "用於", -"用药" => "用藥", -"甩发" => "甩髮", -"田谷" => "田穀", -"田庄" => "田莊", -"田里" => "田裡", -"由于" => "由於", -"男仆" => "男僕", -"男用表" => "男用錶", -"畏于" => "畏於", -"留后" => "留後", -"留发" => "留髮", -"毕于" => "畢於", -"毕业于" => "畢業於", -"毕生发展" => "畢生發展", -"划着" => "畫著", -"异于" => "異於", -"当一天和尚撞一天钟" => "當一天和尚撞一天鐘", -"当家才知柴米价" => "當家纔知柴米價", -"当于" => "當於", -"当当丁丁" => "當當丁丁", -"当着" => "當著", -"疏于" => "疏於", -"疏松" => "疏鬆", -"疑凶" => "疑兇", -"疲于" => "疲於", -"疲于奔命" => "疲於奔命", -"疲困" => "疲睏", -"病后" => "病後", -"病征" => "病徵", -"病症" => "病癥", -"症候" => "癥候", -"症状" => "癥狀", -"症结" => "癥結", -"发干" => "發乾", -"发于" => "發於", -"发汗药" => "發汗藥", -"发呆" => "發獃", -"发签" => "發籤", -"发着" => "發著", -"发松" => "發鬆", -"发面" => "發麵", -"白干" => "白乾", -"白术" => "白朮", -"白朴" => "白樸", -"白发其事" => "白發其事", -"白粉面" => "白粉麵", -"白发" => "白髮", -"白霉" => "白黴", -"百多只" => "百多隻", -"百天后" => "百天後", -"百拙千丑" => "百拙千醜", -"百扎" => "百紮", -"百花历" => "百花曆", -"百只" => "百隻", -"皇历" => "皇曆", -"皇极历" => "皇極曆", -"皇庄" => "皇莊", -"皓发" => "皓髮", -"皮里阳秋" => "皮裏陽秋", -"皮里春秋" => "皮裡春秋", -"皮制" => "皮製", -"皮松" => "皮鬆", -"皱别" => "皺彆", -"皱折" => "皺摺", -"盈余" => "盈餘", -"益于" => "益於", -"盒里" => "盒裡", -"盛德遗范" => "盛德遺範", -"盛行于" => "盛行於", -"盛赞" => "盛讚", -"盗采" => "盜採", -"盗钟" => "盜鐘", -"监制" => "監製", -"盘里" => "盤裡", -"盘回" => "盤迴", -"卢棱伽" => "盧稜伽", -"盲干" => "盲幹", -"直接参与" => "直接參与", -"直于" => "直於", -"直冲" => "直衝", -"相并" => "相併", -"相克" => "相剋", -"相同于" => "相同於", -"相干" => "相干", -"相于" => "相於", -"相冲" => "相衝", -"相斗" => "相鬥", -"看准" => "看準", -"真凶" => "真兇", -"眼帘" => "眼帘", -"眼眶里" => "眼眶裡", -"眼药" => "眼藥", -"眼里" => "眼裡", -"困乏" => "睏乏", -"困觉" => "睏覺", -"睡着了" => "睡著了", -"瞄准" => "瞄準", -"瞠乎后矣" => "瞠乎後矣", -"了望" => "瞭望", -"了然" => "瞭然", -"了若指掌" => "瞭若指掌", -"瞻前顾后" => "瞻前顧後", -"蒙事" => "矇事", -"蒙昧无知" => "矇昧無知", -"蒙松雨" => "矇松雨", -"蒙混" => "矇混", -"蒙瞍" => "矇瞍", -"蒙眬" => "矇矓", -"蒙聩" => "矇聵", -"蒙着" => "矇著", -"蒙着锅儿" => "矇著鍋兒", -"蒙头转" => "矇頭轉", -"蒙骗" => "矇騙", -"瞩讬" => "矚託", -"短于" => "短於", -"短发" => "短髮", -"石棱棱" => "石稜稜", -"石英表" => "石英錶", -"石钟乳" => "石鐘乳", -"石硷" => "石鹼", -"研制" => "研製", -"砰当" => "砰噹", -"朱唇皓齿" => "硃唇皓齒", -"朱批" => "硃批", -"朱砂" => "硃砂", -"朱笔" => "硃筆", -"朱红色" => "硃紅色", -"朱色" => "硃色", -"硫化硷" => "硫化鹼", -"硬干" => "硬幹", -"确瘠" => "确瘠", -"碑志" => "碑誌", -"碰钟" => "碰鐘", -"磁制" => "磁製", -"磨制" => "磨製", -"硗确" => "磽确", -"碍于" => "礙於", -"砻谷机" => "礱穀機", -"示范" => "示範", -"社里" => "社裡", -"祝发" => "祝髮", -"神荼郁垒" => "神荼鬱壘", -"神游" => "神遊", -"神雕像" => "神雕像", -"神雕" => "神鵰", -"祭吊" => "祭弔", -"禁欲" => "禁慾", -"禁药" => "禁藥", -"祸于" => "禍於", -"御侮" => "禦侮", -"御寇" => "禦寇", -"御寒" => "禦寒", -"御敌" => "禦敵", -"礼赞" => "禮讚", -"禾谷" => "禾穀", -"秃妃之发" => "禿妃之髮", -"秃发" => "禿髮", -"秀发" => "秀髮", -"私下里" => "私下裡", -"私欲" => "私慾", -"私斗" => "私鬥", -"秋天里" => "秋天裡", -"秋后" => "秋後", -"秋裤" => "秋褲", -"秋游" => "秋遊", -"秋阴入井干" => "秋陰入井幹", -"秋发" => "秋髮", -"种师中" => "种師中", -"种师道" => "种師道", -"种放" => "种放", -"科范" => "科範", -"秒表" => "秒錶", -"秒钟" => "秒鐘", -"稀松" => "稀鬆", -"稍后" => "稍後", -"棱台" => "稜台", -"棱子" => "稜子", -"棱层" => "稜層", -"棱柱" => "稜柱", -"棱棱" => "稜稜", -"棱棱睁睁" => "稜稜睜睜", -"棱等登" => "稜等登", -"棱线" => "稜線", -"棱缝" => "稜縫", -"棱角" => "稜角", -"棱锥" => "稜錐", -"棱镜" => "稜鏡", -"棱体" => "稜體", -"种谷" => "種穀", -"称赞" => "稱讚", -"稻谷" => "稻穀", -"稽征" => "稽徵", -"谷仓" => "穀倉", -"谷圭" => "穀圭", -"谷场" => "穀場", -"谷子" => "穀子", -"谷日" => "穀日", -"谷旦" => "穀旦", -"谷壳" => "穀殼", -"谷物" => "穀物", -"谷皮" => "穀皮", -"谷神" => "穀神", -"谷米" => "穀米", -"谷粒" => "穀粒", -"谷舱" => "穀艙", -"谷苗" => "穀苗", -"谷草" => "穀草", -"谷贵饿农" => "穀貴餓農", -"谷贱伤农" => "穀賤傷農", -"谷道" => "穀道", -"谷雨" => "穀雨", -"谷类" => "穀類", -"谷食" => "穀食", -"穆罕默德历" => "穆罕默德曆", -"积极参与" => "積极參与", -"积极参加" => "積极參加", -"积谷" => "積穀", -"积郁" => "積鬱", -"稳扎" => "穩紮", -"空荡" => "空蕩", -"空钟" => "空鐘", -"空余" => "空餘", -"窗帘" => "窗帘", -"窗明几净" => "窗明几淨", -"窗台" => "窗檯", -"窝里" => "窩裡", -"穷于" => "窮於", -"穷追不舍" => "窮追不捨", -"窃钟掩耳" => "竊鐘掩耳", -"立于" => "立於", -"立范" => "立範", -"站干岸儿" => "站乾岸兒", -"竟于" => "竟於", -"童仆" => "童僕", -"端庄" => "端莊", -"竞斗" => "競鬥", -"竹签" => "竹籤", -"笆斗" => "笆鬥", -"笑里藏刀" => "笑裡藏刀", -"笔划" => "筆劃", -"等同于" => "等同於", -"等于" => "等於", -"筋斗" => "筋鬥", -"笋干" => "筍乾", -"筑前" => "筑前", -"筑北" => "筑北", -"筑州" => "筑州", -"筑后" => "筑後", -"筑波" => "筑波", -"筑紫" => "筑紫", -"筑肥" => "筑肥", -"筑西" => "筑西", -"筑邦" => "筑邦", -"筑阳" => "筑陽", -"答复" => "答覆", -"策划" => "策劃", -"筵几" => "筵几", -"箕斗" => "箕鬥", -"算发" => "算髮", -"管干" => "管幹", -"节欲" => "節慾", -"节余" => "節餘", -"范例" => "範例", -"范围" => "範圍", -"范式" => "範式", -"范文" => "範文", -"范本" => "範本", -"范畴" => "範疇", -"简朴" => "簡樸", -"签着" => "簽著", -"筹划" => "籌劃", -"签幐" => "籤幐", -"签押" => "籤押", -"签条" => "籤條", -"签诗" => "籤詩", -"吁天" => "籲天", -"吁求" => "籲求", -"吁请" => "籲請", -"米谷" => "米穀", -"粉拳绣腿" => "粉拳繡腿", -"粉签子" => "粉籤子", -"粗制" => "粗製", -"精干" => "精幹", -"精采" => "精採", -"精于" => "精於", -"精明强干" => "精明強幹", -"精准" => "精準", -"精致" => "精緻", -"精制" => "精製", -"精通于" => "精通於", -"精辟" => "精闢", -"精松" => "精鬆", -"糊里糊涂" => "糊裡糊塗", -"糕干" => "糕乾", -"粪秽蔑面" => "糞穢衊面", -"团子" => "糰子", -"系着" => "系著", -"纪元后" => "紀元後", -"纪历" => "紀曆", -"红发" => "紅髮", -"红霉素" => "紅黴素", -"纡回" => "紆迴", -"纡余" => "紆餘", -"纡郁" => "紆鬱", -"纯朴" => "純樸", -"纯硷" => "純鹼", -"纸扎" => "紙紮", -"素朴" => "素樸", -"素藉" => "素藉", -"素食面" => "素食麵", -"素面" => "素麵", -"索面" => "索麵", -"紫姜" => "紫薑", -"扎上" => "紮上", -"扎下" => "紮下", -"扎好" => "紮好", -"扎实" => "紮實", -"扎寨" => "紮寨", -"扎带子" => "紮帶子", -"扎成" => "紮成", -"扎根" => "紮根", -"扎营" => "紮營", -"扎紧" => "紮緊", -"扎起" => "紮起", -"扎铁" => "紮鐵", -"细不容发" => "細不容髮", -"细致" => "細緻", -"终于" => "終於", -"组里" => "組裡", -"结伴同游" => "結伴同遊", -"结伙" => "結夥", -"结扎" => "結紮", -"结余" => "結餘", -"结发" => "結髮", -"绝对参照" => "絕對參照", -"绝后" => "絕後", -"绝于" => "絕於", -"绞干" => "絞乾", -"络绎于途" => "絡繹於途", -"给于" => "給於", -"丝来线去" => "絲來線去", -"丝布" => "絲布", -"丝恩发怨" => "絲恩髮怨", -"丝板" => "絲板", -"丝瓜布" => "絲瓜布", -"丝绒布" => "絲絨布", -"丝线" => "絲線", -"丝织厂" => "絲織廠", -"丝虫" => "絲蟲", -"丝发" => "絲髮", -"绑扎" => "綁紮", -"綑扎" => "綑紮", -"绿发" => "綠髮", -"绿霉素" => "綠黴素", -"绸缎庄" => "綢緞莊", -"维系" => "維繫", -"绾发" => "綰髮", -"网里" => "網裡", -"网志" => "網誌", -"紧绷" => "緊繃", -"紧绷着" => "緊繃著", -"紧追不舍" => "緊追不捨", -"绪余" => "緒餘", -"缉凶" => "緝兇", -"编制" => "編製", -"编钟" => "編鐘", -"编余" => "編餘", -"编发" => "編髮", -"缓征" => "緩徵", -"缓冲" => "緩衝", -"致密" => "緻密", -"萦回" => "縈迴", -"缜致" => "縝緻", -"县里" => "縣裡", -"县志" => "縣誌", -"缝里" => "縫裡", -"缝制" => "縫製", -"缩栗" => "縮慄", -"纵欲" => "縱慾", -"纤夫" => "縴夫", -"繁复" => "繁複", -"绷住" => "繃住", -"绷子" => "繃子", -"绷带" => "繃帶", -"绷紧" => "繃緊", -"绷脸" => "繃臉", -"绷着" => "繃著", -"绷着脸" => "繃著臉", -"绷着脸儿" => "繃著臉兒", -"绷开" => "繃開", -"繐帏飘井干" => "繐幃飄井幹", -"绕梁" => "繞樑", -"绣得" => "繡得", -"绣房" => "繡房", -"绣毯" => "繡毯", -"绣球" => "繡球", -"绣的" => "繡的", -"绣花" => "繡花", -"绣衣" => "繡衣", -"绣起" => "繡起", -"绣阁" => "繡閣", -"绣鞋" => "繡鞋", -"绘制" => "繪製", -"系上" => "繫上", -"系到" => "繫到", -"系囚" => "繫囚", -"系心" => "繫心", -"系念" => "繫念", -"系怀" => "繫懷", -"系于" => "繫於", -"系系" => "繫系", -"系紧" => "繫緊", -"系绳" => "繫繩", -"系着" => "繫著", -"系辞" => "繫辭", -"累囚" => "纍囚", -"累堆" => "纍堆", -"累瓦结绳" => "纍瓦結繩", -"累绁" => "纍紲", -"累臣" => "纍臣", -"缠斗" => "纏鬥", -"才则" => "纔則", -"才可容颜十五余" => "纔可容顏十五餘", -"才得两年" => "纔得兩年", -"才此" => "纔此", -"坛子" => "罈子", -"坛坛罐罐" => "罈罈罐罐", -"坛騞" => "罈騞", -"置于" => "置於", -"骂着" => "罵著", -"美仑" => "美崙", -"美于" => "美於", -"美制" => "美製", -"美丑" => "美醜", -"美发" => "美髮", -"羞于" => "羞於", -"群丑" => "群醜", -"羨余" => "羨餘", -"义仆" => "義僕", -"义形于色" => "義形於色", -"义庄" => "義莊", -"习于" => "習於", -"翕辟" => "翕闢", -"翱游" => "翱遊", -"翻涌" => "翻湧", -"翻云复雨" => "翻雲覆雨", -"翻松" => "翻鬆", -"老干" => "老乾", -"老仆" => "老僕", -"老干部" => "老幹部", -"老蒙" => "老懞", -"老于" => "老於", -"老于世故" => "老於世故", -"老庄" => "老莊", -"老姜" => "老薑", -"老板" => "老闆", -"考后" => "考後", -"而后" => "而後", -"而于" => "而於", -"耐硷" => "耐鹼", -"耕佣" => "耕傭", -"耕获" => "耕穫", -"耳后" => "耳後", -"耳余" => "耳餘", -"耽于" => "耽於", -"耿于" => "耿於", -"耿耿于怀" => "耿耿於懷", -"聊斋志异" => "聊齋志異", -"联系" => "聯係", -"联于" => "聯於", -"联系" => "聯繫", -"声如洪钟" => "聲如洪鐘", -"听于" => "聽於", -"肉干" => "肉乾", -"肉欲" => "肉慾", -"肉丝面" => "肉絲麵", -"肉羹面" => "肉羹麵", -"肉松" => "肉鬆", -"肝郁" => "肝鬱", -"股栗" => "股慄", -"肥筑方言" => "肥筑方言", -"胃药" => "胃藥", -"背向着" => "背向著", -"背地里" => "背地裡", -"背后" => "背後", -"胎发" => "胎髮", -"胜肽" => "胜肽", -"胜键" => "胜鍵", -"胡云" => "胡云", -"胡朴安" => "胡樸安", -"胡里胡涂" => "胡裡胡塗", -"能干" => "能幹", -"脉冲" => "脈衝", -"脊梁" => "脊樑", -"脱谷机" => "脫穀機", -"脱发" => "脫髮", -"腊味" => "腊味", -"腊笔" => "腊筆", -"腐干" => "腐乾", -"脑子里" => "腦子裡", -"脑干" => "腦幹", -"脑后" => "腦後", -"腰里" => "腰裡", -"脚注" => "腳註", -"膏药" => "膏藥", -"臣仆" => "臣僕", -"臣服于" => "臣服於", -"卧游" => "臥遊", -"臧谷亡羊" => "臧穀亡羊", -"自于" => "自於", -"自制" => "自製", -"自觉自愿" => "自覺自愿", -"自鸣钟" => "自鳴鐘", -"至于" => "至於", -"致力于" => "致力於", -"致于" => "致於", -"臻于" => "臻於", -"臻于完善" => "臻於完善", -"舂谷" => "舂穀", -"兴致" => "興緻", -"举手表" => "舉手表", -"旧庄" => "舊庄", -"旧历" => "舊曆", -"旧药" => "舊藥", -"舌干脣焦" => "舌乾脣焦", -"舌后" => "舌後", -"舒卷" => "舒捲", -"航海历" => "航海曆", -"船只" => "船隻", -"舰只" => "艦隻", -"良药" => "良藥", -"色欲" => "色慾", -"艸木丰丰" => "艸木丰丰", -"芍药" => "芍藥", -"芒果干" => "芒果乾", -"花拳绣腿" => "花拳繡腿", -"花卷" => "花捲", -"花盆里" => "花盆裡", -"花药" => "花藥", -"花哄" => "花鬨", -"苑里" => "苑裡", -"苛性硷" => "苛性鹼", -"若于" => "若於", -"苦干" => "苦幹", -"苦于" => "苦於", -"苦药" => "苦藥", -"苦里" => "苦裏", -"苦斗" => "苦鬥", -"苹萦" => "苹縈", -"茵藉" => "茵藉", -"茶几" => "茶几", -"茶庄" => "茶莊", -"茶余" => "茶餘", -"茶面" => "茶麵", -"草丛里" => "草叢裡", -"草药" => "草藥", -"荷花淀" => "荷花澱", -"庄主" => "莊主", -"庄周" => "莊周", -"庄员" => "莊員", -"庄严" => "莊嚴", -"庄园" => "莊園", -"庄子" => "莊子", -"庄家" => "莊家", -"庄户" => "莊戶", -"庄敬" => "莊敬", -"庄田" => "莊田", -"庄稼" => "莊稼", -"庄里" => "莊裡", -"庄重" => "莊重", -"庄院" => "莊院", -"茎干" => "莖幹", -"莽荡" => "莽蕩", -"菌丝体" => "菌絲體", -"菜干" => "菜乾", -"菠棱菜" => "菠稜菜", -"菠萝干" => "菠蘿乾", -"华发" => "華髮", -"菸硷" => "菸鹼", -"万多只" => "萬多隻", -"万天后" => "萬天後", -"万历" => "萬曆", -"万签插架" => "萬籤插架", -"万扎" => "萬紮", -"万象" => "萬象", -"万只" => "萬隻", -"万余" => "萬餘", -"落后" => "落後", -"落于" => "落於", -"落发" => "落髮", -"着儿" => "著兒", -"着书立说" => "著書立說", -"着色软体" => "著色軟體", -"着重指出" => "著重指出", -"着录" => "著錄", -"着录规则" => "著錄規則", -"葡萄干" => "葡萄乾", -"董氏封发" => "董氏封髮", -"蒙汗药" => "蒙汗藥", -"蒙雾露" => "蒙霧露", -"蒜发" => "蒜髮", -"苍术" => "蒼朮", -"苍郁" => "蒼鬱", -"蓄发" => "蓄髮", -"蓄须" => "蓄鬚", -"蓊郁" => "蓊鬱", -"盖于" => "蓋於", -"蓬发" => "蓬髮", -"蓬松" => "蓬鬆", -"参绥" => "蔘綏", -"荞麦面" => "蕎麥麵", -"荡来荡去" => "蕩來蕩去", -"荡女" => "蕩女", -"荡妇" => "蕩婦", -"荡寇" => "蕩寇", -"荡平" => "蕩平", -"荡涤" => "蕩滌", -"荡漾" => "蕩漾", -"荡然" => "蕩然", -"荡舟" => "蕩舟", -"荡船" => "蕩船", -"荡荡" => "蕩蕩", -"萧参" => "蕭蔘", -"薄幸" => "薄倖", -"薄干" => "薄幹", -"姜是老的辣" => "薑是老的辣", -"姜末" => "薑末", -"姜桂" => "薑桂", -"姜母" => "薑母", -"姜汁" => "薑汁", -"姜汤" => "薑湯", -"姜片" => "薑片", -"姜糖" => "薑糖", -"姜丝" => "薑絲", -"姜老辣" => "薑老辣", -"姜蓉" => "薑蓉", -"姜饼" => "薑餅", -"姜黄" => "薑黃", -"薙发" => "薙髮", -"薝卜" => "薝蔔", -"藉以" => "藉以", -"藉卉" => "藉卉", -"藉寇兵" => "藉寇兵", -"藉手" => "藉手", -"藉槁" => "藉槁", -"藉机" => "藉機", -"藉此" => "藉此", -"藉甚" => "藉甚", -"藉由" => "藉由", -"藉箸代筹" => "藉箸代籌", -"藉草枕块" => "藉草枕塊", -"藉着" => "藉著", -"藉藉" => "藉藉", -"藉资" => "藉資", -"藏匿于" => "藏匿於", -"藏于" => "藏於", -"藏历" => "藏曆", -"藏蒙歌儿" => "藏矇歌兒", -"藤制" => "藤製", -"药丸" => "藥丸", -"药典" => "藥典", -"药到病除" => "藥到病除", -"药剂" => "藥劑", -"药力" => "藥力", -"药包" => "藥包", -"药名" => "藥名", -"药味" => "藥味", -"药品" => "藥品", -"药商" => "藥商", -"药单" => "藥單", -"药婆" => "藥婆", -"药学" => "藥學", -"药害" => "藥害", -"药专" => "藥專", -"药局" => "藥局", -"药师" => "藥師", -"药店" => "藥店", -"药厂" => "藥廠", -"药引" => "藥引", -"药性" => "藥性", -"药房" => "藥房", -"药效" => "藥效", -"药方" => "藥方", -"药材" => "藥材", -"药棉" => "藥棉", -"药水" => "藥水", -"药油" => "藥油", -"药液" => "藥液", -"药渣" => "藥渣", -"药片" => "藥片", -"药物" => "藥物", -"药王" => "藥王", -"药理" => "藥理", -"药瓶" => "藥瓶", -"药用" => "藥用", -"药皂" => "藥皂", -"药盒" => "藥盒", -"药石" => "藥石", -"药科" => "藥科", -"药箱" => "藥箱", -"药签" => "藥籤", -"药粉" => "藥粉", -"药糖" => "藥糖", -"药线" => "藥線", -"药罐" => "藥罐", -"药膏" => "藥膏", -"药舖" => "藥舖", -"药茶" => "藥茶", -"药草" => "藥草", -"药行" => "藥行", -"药贩" => "藥販", -"药费" => "藥費", -"药酒" => "藥酒", -"药量" => "藥量", -"药针" => "藥針", -"药铺" => "藥鋪", -"药头" => "藥頭", -"药饵" => "藥餌", -"药面儿" => "藥麵兒", -"苏昆" => "蘇崑", -"蕴含着" => "蘊含著", -"蕴涵着" => "蘊涵著", -"蕴藉" => "蘊藉", -"苹果干" => "蘋果乾", -"萝卜" => "蘿蔔", -"萝卜干" => "蘿蔔乾", -"虎须" => "虎鬚", -"处于" => "處於", -"号志" => "號誌", -"虫部" => "虫部", -"蛇发女妖" => "蛇髮女妖", -"蛔虫药" => "蛔蟲藥", -"蜂涌" => "蜂湧", -"蛏干" => "蟶乾", -"蛮干" => "蠻幹", -"血余" => "血餘", -"行事历" => "行事曆", -"行凶" => "行兇", -"行凶后" => "行兇後", -"行于" => "行於", -"胡同" => "衚衕", -"冲上" => "衝上", -"冲下" => "衝下", -"冲来" => "衝來", -"冲倒" => "衝倒", -"冲出" => "衝出", -"冲到" => "衝到", -"冲刺" => "衝刺", -"冲克" => "衝剋", -"冲力" => "衝力", -"冲劲" => "衝勁", -"冲动" => "衝動", -"冲去" => "衝去", -"冲口" => "衝口", -"冲垮" => "衝垮", -"冲堂" => "衝堂", -"冲坚陷阵" => "衝堅陷陣", -"冲压" => "衝壓", -"冲天" => "衝天", -"冲州撞府" => "衝州撞府", -"冲心" => "衝心", -"冲掉" => "衝掉", -"冲撞" => "衝撞", -"冲击" => "衝擊", -"冲散" => "衝散", -"冲杀" => "衝殺", -"冲决" => "衝決", -"冲波" => "衝波", -"冲浪" => "衝浪", -"冲激" => "衝激", -"冲然" => "衝然", -"冲盹" => "衝盹", -"冲破" => "衝破", -"冲程" => "衝程", -"冲突" => "衝突", -"冲线" => "衝線", -"冲着" => "衝著", -"冲要" => "衝要", -"冲起" => "衝起", -"冲车" => "衝車", -"冲进" => "衝進", -"冲过" => "衝過", -"冲量" => "衝量", -"冲锋" => "衝鋒", -"冲陷" => "衝陷", -"冲头阵" => "衝頭陣", -"冲风" => "衝風", -"表征" => "表徵", -"表里" => "表裡", -"衷于" => "衷於", -"袖里" => "袖裡", -"被里" => "被裡", -"被复" => "被複", -"被复着" => "被覆著", -"裁并" => "裁併", -"裁制" => "裁製", -"里勾外连" => "裏勾外連", -"里手" => "裏手", -"里海" => "裏海", -"里面" => "裏面", -"补药" => "補藥", -"补血药" => "補血藥", -"补注" => "補註", -"里外" => "裡外", -"里子" => "裡子", -"里屋" => "裡屋", -"里层" => "裡層", -"里布" => "裡布", -"里带" => "裡帶", -"里弦" => "裡弦", -"里应外合" => "裡應外合", -"里脊" => "裡脊", -"里衣" => "裡衣", -"里通外国" => "裡通外國", -"里通外敌" => "裡通外敵", -"里边" => "裡邊", -"里间" => "裡間", -"里面" => "裡面", -"里头" => "裡頭", -"制件" => "製件", -"制作" => "製作", -"制做" => "製做", -"制备" => "製備", -"制冰" => "製冰", -"制冷" => "製冷", -"制剂" => "製劑", -"制品" => "製品", -"制图" => "製圖", -"制成" => "製成", -"制法" => "製法", -"制为" => "製為", -"制片" => "製片", -"制版" => "製版", -"制程" => "製程", -"制糖" => "製糖", -"制纸" => "製紙", -"制药" => "製藥", -"制表" => "製表", -"制裁" => "製裁", -"制造" => "製造", -"制革" => "製革", -"制鞋" => "製鞋", -"制盐" => "製鹽", -"复仞年如" => "複仞年如", -"复以百万" => "複以百萬", -"复位" => "複位", -"复信" => "複信", -"复元音" => "複元音", -"复分数" => "複分數", -"复分析" => "複分析", -"复列" => "複列", -"复利" => "複利", -"复印" => "複印", -"复原" => "複原", -"复句" => "複句", -"复合" => "複合", -"复名" => "複名", -"复员" => "複員", -"复壁" => "複壁", -"复壮" => "複壯", -"复姓" => "複姓", -"复字键" => "複字鍵", -"复审" => "複審", -"复写" => "複寫", -"复平面" => "複平面", -"复式" => "複式", -"复复" => "複復", -"复数" => "複數", -"复本" => "複本", -"复查" => "複查", -"复核" => "複核", -"复检" => "複檢", -"复次" => "複次", -"复比" => "複比", -"复决" => "複決", -"复流" => "複流", -"复测" => "複測", -"复亩珍" => "複畝珍", -"复发" => "複發", -"复目" => "複目", -"复眼" => "複眼", -"复种" => "複種", -"复线" => "複線", -"复习" => "複習", -"复色" => "複色", -"复叶" => "複葉", -"复盖" => "複蓋", -"复制" => "複製", -"复诊" => "複診", -"复词" => "複詞", -"复试" => "複試", -"复课" => "複課", -"复议" => "複議", -"复变函数" => "複變函數", -"复赛" => "複賽", -"复辅音" => "複輔音", -"复述" => "複述", -"复选" => "複選", -"复钱" => "複錢", -"复杂" => "複雜", -"复电" => "複電", -"复音" => "複音", -"复韵" => "複韻", -"衬里" => "襯裡", -"西岳" => "西嶽", -"西历" => "西曆", -"西历史" => "西歷史", -"西药" => "西藥", -"西游" => "西遊", -"要冲" => "要衝", -"要么" => "要麼", -"复亡" => "覆亡", -"复命" => "覆命", -"复巢之下无完卵" => "覆巢之下無完卵", -"复水难收" => "覆水難收", -"复没" => "覆沒", -"复着" => "覆著", -"复盖" => "覆蓋", -"复盖着" => "覆蓋著", -"复辙" => "覆轍", -"复雨翻云" => "覆雨翻雲", -"见于" => "見於", -"见棱见角" => "見稜見角", -"见素抱朴" => "見素抱樸", -"见钟不打" => "見鐘不打", -"规划" => "規劃", -"规范" => "規範", -"视于" => "視於", -"观采" => "觀採", -"角落发" => "角落發", -"角落里" => "角落裡", -"觚棱" => "觚稜", -"解雇" => "解僱", -"解痛药" => "解痛藥", -"解药" => "解藥", -"解发佯狂" => "解髮佯狂", -"触须" => "觸鬚", -"言大而夸" => "言大而夸", -"言辩而确" => "言辯而确", -"订于" => "訂於", -"订制" => "訂製", -"计划" => "計劃", -"讬了" => "託了", -"讬事" => "託事", -"讬交" => "託交", -"讬人" => "託人", -"讬付" => "託付", -"讬古讽今" => "託古諷今", -"讬名" => "託名", -"讬咎" => "託咎", -"讬梦" => "託夢", -"讬大" => "託大", -"讬孤" => "託孤", -"讬故" => "託故", -"讬疾" => "託疾", -"讬病" => "託病", -"讬福" => "託福", -"讬管" => "託管", -"讬言" => "託言", -"讬词" => "託詞", -"讬买" => "託買", -"讬卖" => "託賣", -"讬身" => "託身", -"讬辞" => "託辭", -"讬运" => "託運", -"讬过" => "託過", -"设于" => "設於", -"许愿起经" => "許愿起經", -"诉说着" => "訴說著", -"注上" => "註上", -"注册" => "註冊", -"注失" => "註失", -"注定" => "註定", -"注明" => "註明", -"注标" => "註標", -"注生娘娘" => "註生娘娘", -"注疏" => "註疏", -"注脚" => "註腳", -"注解" => "註解", -"注译" => "註譯", -"注释" => "註釋", -"注销" => "註銷", -"评注" => "評註", -"词干" => "詞幹", -"词汇" => "詞彙", -"词余" => "詞餘", -"询于" => "詢於", -"诗云" => "詩云", -"诗钟" => "詩鐘", -"诗余" => "詩餘", -"话里有话" => "話裡有話", -"该于" => "該於", -"详注" => "詳註", -"夸赞" => "誇讚", -"志哀" => "誌哀", -"志喜" => "誌喜", -"志庆" => "誌慶", -"志异" => "誌異", -"诱奸" => "誘姦", -"语云" => "語云", -"语汇" => "語彙", -"诚征" => "誠徵", -"诚朴" => "誠樸", -"诬蔑" => "誣衊", -"说着" => "說著", -"课后" => "課後", -"课征" => "課徵", -"课余" => "課餘", -"调准" => "調準", -"调制" => "調製", -"请参阅" => "請參閱", -"请讬" => "請託", -"诸余" => "諸餘", -"谋干" => "謀幹", -"谢绝参观" => "謝絕參觀", -"谬采虚声" => "謬採虛聲", -"謷丑" => "謷醜", -"证于" => "證於", -"警世钟" => "警世鐘", -"警钟" => "警鐘", -"译注" => "譯註", -"护发" => "護髮", -"读后" => "讀後", -"变丑" => "變醜", -"雠隙" => "讎隙", -"赞不绝口" => "讚不絕口", -"赞佩" => "讚佩", -"赞同" => "讚同", -"赞叹" => "讚嘆", -"赞扬" => "讚揚", -"赞乐" => "讚樂", -"赞歌" => "讚歌", -"赞歎" => "讚歎", -"赞美" => "讚美", -"赞羨" => "讚羨", -"赞许" => "讚許", -"赞词" => "讚詞", -"赞誉" => "讚譽", -"赞赏" => "讚賞", -"赞辞" => "讚辭", -"赞颂" => "讚頌", -"豆干" => "豆乾", -"豆腐干" => "豆腐乾", -"竖着" => "豎著", -"丰滨" => "豐濱", -"丰滨乡" => "豐濱鄉", -"象征" => "象徵", -"象征着" => "象徵著", -"负债累累" => "負債纍纍", -"贪欲" => "貪慾", -"贵干" => "貴幹", -"买凶" => "買兇", -"贻范" => "貽範", -"贾后" => "賈後", -"赈饥" => "賑饑", -"质朴" => "質樸", -"赌台" => "賭檯", -"赖于" => "賴於", -"賸余" => "賸餘", -"购并" => "購併", -"购买欲" => "購買慾", -"赢余" => "贏餘", -"走后" => "走後", -"起因于" => "起因於", -"起复" => "起複", -"起哄" => "起鬨", -"赶制" => "趕製", -"赶面棍" => "趕麵棍", -"赵庄" => "趙莊", -"趋于" => "趨於", -"趱干" => "趲幹", -"足于" => "足於", -"跌荡" => "跌蕩", -"跟前跟后" => "跟前跟後", -"路签" => "路籤", -"跳荡" => "跳蕩", -"跳表" => "跳錶", -"蹭棱子" => "蹭稜子", -"躁郁" => "躁鬱", -"躏藉" => "躪藉", -"身后" => "身後", -"身于" => "身於", -"躯干" => "軀幹", -"车库里" => "車庫裡", -"车站里" => "車站裡", -"车里" => "車裡", -"轨范" => "軌範", -"轩辟" => "軒闢", -"载于" => "載於", -"挽曲" => "輓曲", -"挽歌" => "輓歌", -"挽联" => "輓聯", -"挽词" => "輓詞", -"挽诗" => "輓詩", -"轻于" => "輕於", -"轻松" => "輕鬆", -"轮奸" => "輪姦", -"轮回" => "輪迴", -"转台" => "轉檯", -"转讬" => "轉託", -"辟谷" => "辟穀", -"办公台" => "辦公檯", -"辞汇" => "辭彙", -"辫发" => "辮髮", -"农历" => "農曆", -"农民历" => "農民曆", -"农庄" => "農莊", -"农药" => "農藥", -"迂回" => "迂迴", -"近似于" => "近似於", -"近于" => "近於", -"近日里" => "近日裡", -"返朴" => "返樸", -"迥然回异" => "迥然迴異", -"迫于" => "迫於", -"回光返照" => "迴光返照", -"回向" => "迴向", -"回圈" => "迴圈", -"回廊" => "迴廊", -"回形夹" => "迴形夾", -"回文" => "迴文", -"回旋" => "迴旋", -"回流" => "迴流", -"回环" => "迴環", -"回荡" => "迴盪", -"回纹针" => "迴紋針", -"回绕" => "迴繞", -"回肠" => "迴腸", -"回荡" => "迴蕩", -"回诵" => "迴誦", -"回路" => "迴路", -"回转" => "迴轉", -"回递性" => "迴遞性", -"回避" => "迴避", -"回响" => "迴響", -"回风" => "迴風", -"迷幻药" => "迷幻藥", -"迷于" => "迷於", -"迷蒙" => "迷濛", -"迷药" => "迷藥", -"迷魂药" => "迷魂藥", -"追凶" => "追兇", -"退后" => "退後", -"退烧药" => "退燒藥", -"逋发" => "逋髮", -"透辟" => "透闢", -"这里" => "這裏", -"这里" => "這裡", -"这只" => "這隻", -"这么" => "這麼", -"这么着" => "這麼著", -"通奸" => "通姦", -"通心面" => "通心麵", -"通于" => "通於", -"通历" => "通曆", -"速食面" => "速食麵", -"连三并四" => "連三併四", -"连占" => "連佔", -"连采" => "連採", -"连于" => "連於", -"连系" => "連繫", -"连庄" => "連莊", -"周游" => "週遊", -"进占" => "進佔", -"逼并" => "逼併", -"游了" => "遊了", -"游人" => "遊人", -"游伴" => "遊伴", -"游侠" => "遊俠", -"游动" => "遊動", -"游园" => "遊園", -"游子" => "遊子", -"游学" => "遊學", -"游客" => "遊客", -"游宦" => "遊宦", -"游山玩水" => "遊山玩水", -"游必有方" => "遊必有方", -"游憩" => "遊憩", -"游戏" => "遊戲", -"游手好闲" => "遊手好閑", -"游手好闲" => "遊手好閒", -"游星" => "遊星", -"游乐" => "遊樂", -"游标卡尺" => "遊標卡尺", -"游历" => "遊歷", -"游民" => "遊民", -"游牧" => "遊牧", -"游猎" => "遊獵", -"游玩" => "遊玩", -"游荡" => "遊盪", -"游丝" => "遊絲", -"游兴" => "遊興", -"游船" => "遊船", -"游艇" => "遊艇", -"游荡" => "遊蕩", -"游艺" => "遊藝", -"游行" => "遊行", -"游街" => "遊街", -"游览" => "遊覽", -"游记" => "遊記", -"游说" => "遊說", -"游资" => "遊資", -"游走" => "遊走", -"游踪" => "遊蹤", -"游逛" => "遊逛", -"游错" => "遊錯", -"游离" => "遊離", -"游骑兵" => "遊騎兵", -"游魂" => "遊魂", -"遍于" => "遍於", -"过后" => "過後", -"过于" => "過於", -"过水面" => "過水麵", -"道范" => "道範", -"逊于" => "遜於", -"递回" => "遞迴", -"远于" => "遠於", -"远县才至" => "遠縣纔至", -"远游" => "遠遊", -"遨游" => "遨遊", -"适于" => "適於", -"遮丑" => "遮醜", -"迁怒于" => "遷怒於", -"迁于" => "遷於", -"遗范" => "遺範", -"遗余" => "遺餘", -"辽沈" => "遼瀋", -"避孕药" => "避孕藥", -"还占" => "還佔", -"还采" => "還採", -"还政于民" => "還政於民", -"还于" => "還於", -"还冲" => "還衝", -"邋里邋遢" => "邋裡邋遢", -"那里" => "那裡", -"那只" => "那隻", -"那么" => "那麼", -"那么着" => "那麼著", -"郁朴" => "郁樸", -"郊游" => "郊遊", -"郘钟" => "郘鐘", -"部落发" => "部落發", -"都于" => "都於", -"乡愿" => "鄉愿", -"郑凯云" => "鄭凱云", -"配合着" => "配合著", -"配水干管" => "配水幹管", -"配药" => "配藥", -"配制" => "配製", -"酒后" => "酒後", -"酒杯" => "酒盃", -"酒坛" => "酒罈", -"酒药" => "酒藥", -"酒麴" => "酒麴", -"酥松" => "酥鬆", -"酸硷" => "酸鹼", -"醇朴" => "醇樸", -"醉心于" => "醉心於", -"醉于" => "醉於", -"醋坛" => "醋罈", -"丑丫头" => "醜丫頭", -"丑事" => "醜事", -"丑人" => "醜人", -"丑八怪" => "醜八怪", -"丑剌剌" => "醜剌剌", -"丑剧" => "醜劇", -"丑化" => "醜化", -"丑名" => "醜名", -"丑咤" => "醜吒", -"丑地" => "醜地", -"丑夷" => "醜夷", -"丑女效颦" => "醜女效顰", -"丑妇" => "醜婦", -"丑媳" => "醜媳", -"丑小鸭" => "醜小鴨", -"丑巴怪" => "醜巴怪", -"丑恶" => "醜惡", -"丑态" => "醜態", -"丑于" => "醜於", -"丑末" => "醜末", -"丑样" => "醜樣", -"丑死" => "醜死", -"丑生" => "醜生", -"丑闻" => "醜聞", -"丑声四溢" => "醜聲四溢", -"丑声远播" => "醜聲遠播", -"丑脸" => "醜臉", -"丑行" => "醜行", -"丑诋" => "醜詆", -"丑话" => "醜話", -"丑语" => "醜語", -"丑丑" => "醜醜", -"丑陋" => "醜陋", -"丑头怪脸" => "醜頭怪臉", -"丑类" => "醜類", -"酝藉" => "醞藉", -"酝酿着" => "醞釀著", -"医药" => "醫藥", -"酿制" => "釀製", -"衅钟" => "釁鐘", -"采石之役" => "采石之役", -"采石之战" => "采石之戰", -"采石矶" => "采石磯", -"釉药" => "釉藥", -"里程表" => "里程錶", -"重划" => "重劃", -"重折" => "重摺", -"重于" => "重於", -"重罗面" => "重羅麵", -"重复" => "重複", -"重讬" => "重託", -"重游" => "重遊", -"重锤" => "重鎚", -"野姜" => "野薑", -"野游" => "野遊", -"厘出" => "釐出", -"厘定" => "釐定", -"厘正" => "釐正", -"厘清" => "釐清", -"厘订" => "釐訂", -"金仆姑" => "金僕姑", -"金仑溪" => "金崙溪", -"金表" => "金錶", -"金钟" => "金鐘", -"金鸡纳硷" => "金雞納鹼", -"金发" => "金髮", -"金斗" => "金鬥", -"金霉素" => "金黴素", -"钉锤" => "釘鎚", -"铃响后" => "鈴響後", -"钩心斗角" => "鉤心鬥角", -"银朱" => "銀硃", -"银发" => "銀髮", -"铜制" => "銅製", -"铜钟" => "銅鐘", -"铝制" => "鋁製", -"钢梁" => "鋼樑", -"钢制" => "鋼製", -"录着" => "錄著", -"录制" => "錄製", -"钱谷" => "錢穀", -"钱庄" => "錢莊", -"锦心绣口" => "錦心繡口", -"锦绣花园" => "錦綉花園", -"锦绣" => "錦繡", -"表带" => "錶帶", -"表店" => "錶店", -"表厂" => "錶廠", -"表板" => "錶板", -"表壳" => "錶殼", -"表盘" => "錶盤", -"表蒙子" => "錶蒙子", -"表针" => "錶針", -"表链" => "錶鏈", -"锻鍊出" => "鍛鍊出", -"锲而不舍" => "鍥而不捨", -"钟表" => "鍾錶", -"锤儿" => "鎚兒", -"锤子" => "鎚子", -"锤头" => "鎚頭", -"链霉素" => "鏈黴素", -"锈病" => "鏽病", -"锈菌" => "鏽菌", -"锈蚀" => "鏽蝕", -"钟不扣不鸣" => "鐘不扣不鳴", -"钟不撞不鸣" => "鐘不撞不鳴", -"钟乳洞" => "鐘乳洞", -"钟乳石" => "鐘乳石", -"钟在寺里" => "鐘在寺里", -"钟塔" => "鐘塔", -"钟山" => "鐘山", -"钟形虫" => "鐘形蟲", -"钟摆" => "鐘擺", -"钟楼" => "鐘樓", -"钟漏" => "鐘漏", -"钟琴" => "鐘琴", -"钟相" => "鐘相", -"钟磬" => "鐘磬", -"钟声" => "鐘聲", -"钟表" => "鐘表", -"钟表" => "鐘錶", -"钟关" => "鐘關", -"钟响" => "鐘響", -"钟头" => "鐘頭", -"钟鸣" => "鐘鳴", -"钟鸣漏尽" => "鐘鳴漏盡", -"钟点" => "鐘點", -"钟鼎" => "鐘鼎", -"钟鼓" => "鐘鼓", -"铁锤" => "鐵鎚", -"铁锈" => "鐵鏽", -"铸钟" => "鑄鐘", -"鑑于" => "鑑於", -"鉴于" => "鑒於", -"长于" => "長於", -"长历" => "長曆", -"长生药" => "長生藥", -"长须鲸" => "長鬚鯨", -"门前门后" => "門前門後", -"门帘" => "門帘", -"门里" => "門裡", -"门斗" => "門鬥", -"开列于后" => "開列於後", -"开吊" => "開弔", -"开征" => "開徵", -"开采" => "開採", -"开药" => "開藥", -"开辟" => "開闢", -"开哄" => "開鬨", -"闲情逸致" => "閒情逸緻", -"闲荡" => "閒蕩", -"闲游" => "閒遊", -"间不容发" => "間不容髮", -"闵采尔" => "閔採爾", -"合府" => "閤府", -"闺范" => "閨範", -"阃范" => "閫範", -"关系" => "關係", -"关系着" => "關係著", -"关弓与我确" => "關弓與我确", -"关于" => "關於", -"辟佛" => "闢佛", -"辟作" => "闢作", -"辟划" => "闢劃", -"辟土" => "闢土", -"辟地" => "闢地", -"辟室" => "闢室", -"辟建" => "闢建", -"辟为" => "闢為", -"辟田" => "闢田", -"辟筑" => "闢築", -"辟谣" => "闢謠", -"辟辟" => "闢辟", -"辟邪以律" => "闢邪以律", -"防御" => "防禦", -"防范" => "防範", -"防锈" => "防鏽", -"防台" => "防颱", -"阻于" => "阻於", -"附于" => "附於", -"附注" => "附註", -"降压药" => "降壓藥", -"降于" => "降於", -"限于" => "限於", -"升官" => "陞官", -"除臭药" => "除臭藥", -"阴干" => "陰乾", -"阴历" => "陰曆", -"阴郁" => "陰鬱", -"陷于" => "陷於", -"陆游" => "陸遊", -"阳春面" => "陽春麵", -"阳历" => "陽曆", -"随后" => "隨後", -"随于" => "隨於", -"隐几" => "隱几", -"隐于" => "隱於", -"只字" => "隻字", -"只影" => "隻影", -"只手遮天" => "隻手遮天", -"只眼" => "隻眼", -"只言片语" => "隻言片語", -"只身" => "隻身", -"雅范" => "雅範", -"雅致" => "雅緻", -"集于" => "集於", -"集于一身" => "集於一身", -"集游法" => "集遊法", -"雇佣" => "雇傭", -"雇于" => "雇於", -"雕梁划栋" => "雕樑畫棟", -"双折" => "雙摺", -"双胜类" => "雙胜類", -"杂合面儿" => "雜合麵兒", -"杂志" => "雜誌", -"杂面" => "雜麵", -"鸡奸" => "雞姦", -"鸡丝" => "雞絲", -"鸡丝面" => "雞絲麵", -"鸡腿面" => "雞腿麵", -"鸡只" => "雞隻", -"离于" => "離於", -"难容于" => "難容於", -"难舍" => "難捨", -"难于" => "難於", -"雨后" => "雨後", -"雪窗萤几" => "雪窗螢几", -"雪里" => "雪裡", -"云南白药" => "雲南白藥", -"云笈七签" => "雲笈七籤", -"云游" => "雲遊", -"云须" => "雲鬚", -"零多只" => "零多隻", -"零天后" => "零天後", -"零只" => "零隻", -"电子表" => "電子錶", -"电子钟" => "電子鐘", -"电冲" => "電衝", -"电表" => "電錶", -"电钟" => "電鐘", -"震栗" => "震慄", -"震于" => "震於", -"震荡" => "震蕩", -"雾里" => "霧裡", -"露丑" => "露醜", -"霸占" => "霸佔", -"霁范" => "霽範", -"灵药" => "靈藥", -"青山一发" => "青山一髮", -"青苹" => "青苹", -"青霉" => "青黴", -"非占不可" => "非佔不可", -"非于" => "非於", -"靠后" => "靠後", -"面朝着" => "面朝著", -"面临着" => "面臨著", -"鞋里" => "鞋裡", -"鞣制" => "鞣製", -"秋千" => "鞦韆", -"鞭辟入里" => "鞭辟入裡", -"韩国制" => "韓國製", -"韩制" => "韓製", -"音准" => "音準", -"音声如钟" => "音聲如鐘", -"韶山冲" => "韶山衝", -"顺于" => "順於", -"颂赞" => "頌讚", -"预制" => "預製", -"领袖欲" => "領袖慾", -"头里" => "頭裡", -"头发" => "頭髮", -"颊须" => "頰鬚", -"额我略历" => "額我略曆", -"颜范" => "顏範", -"颠干倒坤" => "顛乾倒坤", -"颠复" => "顛覆", -"类似于" => "類似於", -"顾藉" => "顧藉", -"颤栗" => "顫慄", -"显着标志" => "顯著標志", -"风干" => "風乾", -"风土志" => "風土誌", -"风尘仆仆" => "風塵僕僕", -"风卷残云" => "風捲殘雲", -"风物志" => "風物誌", -"风范" => "風範", -"风里" => "風裡", -"风起云涌" => "風起雲湧", -"风斗" => "風鬥", -"台风" => "颱風", -"刮了" => "颳了", -"刮倒" => "颳倒", -"刮去" => "颳去", -"刮得" => "颳得", -"刮着" => "颳著", -"刮走" => "颳走", -"刮起" => "颳起", -"刮雪" => "颳雪", -"刮风" => "颳風", -"飘荡" => "飄蕩", -"飘游" => "飄遊", -"食欲" => "食慾", -"食野之苹" => "食野之苹", -"食面" => "食麵", -"饭后" => "飯後", -"饭后钟" => "飯後鐘", -"饭团" => "飯糰", -"饭庄" => "飯莊", -"饲喂" => "飼餵", -"饼干" => "餅乾", -"馂余" => "餕餘", -"余下" => "餘下", -"余事" => "餘事", -"余人" => "餘人", -"余值" => "餘值", -"余僇" => "餘僇", -"余光" => "餘光", -"余函数" => "餘函數", -"余刃" => "餘刃", -"余切" => "餘切", -"余利" => "餘利", -"余剩" => "餘剩", -"余割" => "餘割", -"余力" => "餘力", -"余勇" => "餘勇", -"余味" => "餘味", -"余喘" => "餘喘", -"余地" => "餘地", -"余址" => "餘址", -"余墨" => "餘墨", -"余外" => "餘外", -"余妙" => "餘妙", -"余姚" => "餘姚", -"余威" => "餘威", -"余存" => "餘存", -"余孽" => "餘孽", -"余巾" => "餘巾", -"余式定理" => "餘式定理", -"余弦" => "餘弦", -"余思" => "餘思", -"余悸" => "餘悸", -"余庆" => "餘慶", -"余数" => "餘數", -"余日" => "餘日", -"余明" => "餘明", -"余映" => "餘映", -"余暇" => "餘暇", -"余晖" => "餘暉", -"余杭" => "餘杭", -"余杯" => "餘杯", -"余桃" => "餘桃", -"余桶" => "餘桶", -"余业" => "餘業", -"余款" => "餘款", -"余步" => "餘步", -"余殃" => "餘殃", -"余毒" => "餘毒", -"余气" => "餘氣", -"余氯" => "餘氯", -"余波" => "餘波", -"余温" => "餘溫", -"余泽" => "餘澤", -"余沥" => "餘瀝", -"余烈" => "餘烈", -"余热" => "餘熱", -"余烬" => "餘燼", -"余珍" => "餘珍", -"余生" => "餘生", -"余留" => "餘留", -"余众" => "餘眾", -"余窍" => "餘竅", -"余粮" => "餘糧", -"余绪" => "餘緒", -"余缺" => "餘缺", -"余罪" => "餘罪", -"余羨" => "餘羨", -"余声" => "餘聲", -"余膏" => "餘膏", -"余兴" => "餘興", -"余蓄" => "餘蓄", -"余荫" => "餘蔭", -"余裕" => "餘裕", -"余角" => "餘角", -"余论" => "餘論", -"余责" => "餘責", -"余辉" => "餘輝", -"余辜" => "餘辜", -"余酲" => "餘酲", -"余钱" => "餘錢", -"余闰" => "餘閏", -"余闲" => "餘閒", -"余震" => "餘震", -"余音" => "餘音", -"余韵" => "餘韻", -"余响" => "餘響", -"余额" => "餘額", -"余风" => "餘風", -"余食" => "餘食", -"余香" => "餘香", -"余党" => "餘黨", -"馄饨面" => "餛飩麵", -"馆谷" => "館穀", -"喂乳" => "餵乳", -"喂了" => "餵了", -"喂奶" => "餵奶", -"喂给" => "餵給", -"喂羊" => "餵羊", -"喂猪" => "餵豬", -"喂过" => "餵過", -"喂鸡" => "餵雞", -"喂食" => "餵食", -"喂饱" => "餵飽", -"喂养" => "餵養", -"喂驴" => "餵驢", -"喂鱼" => "餵魚", -"喂鸭" => "餵鴨", -"喂鹅" => "餵鵝", -"饥寒" => "饑寒", -"饥民" => "饑民", -"饥渴" => "饑渴", -"饥溺" => "饑溺", -"饥饱" => "饑飽", -"饥馑" => "饑饉", -"首当其冲" => "首當其衝", -"香干" => "香乾", -"马干" => "馬乾", -"马后" => "馬後", -"马表" => "馬錶", -"驻扎" => "駐紮", -"骀荡" => "駘蕩", -"骀藉" => "駘藉", -"腾冲" => "騰衝", -"惊赞" => "驚讚", -"骨子里" => "骨子裡", -"骨干" => "骨幹", -"骨灰坛" => "骨灰罈", -"骨坛" => "骨罈", -"骨头里挣出来的钱才做得肉" => "骨頭裡掙出來的錢纔做得肉", -"肮脏" => "骯髒", -"脏乱" => "髒亂", -"脏兮兮" => "髒兮兮", -"脏字" => "髒字", -"脏得" => "髒得", -"脏心" => "髒心", -"脏东西" => "髒東西", -"脏水" => "髒水", -"脏的" => "髒的", -"脏词" => "髒詞", -"脏话" => "髒話", -"脏钱" => "髒錢", -"体范" => "體範", -"高几" => "高几", -"高干" => "高幹", -"高于" => "高於", -"高升" => "高陞", -"髡发" => "髡髮", -"髭须" => "髭鬚", -"发上指冠" => "髮上指冠", -"发上冲冠" => "髮上沖冠", -"发乳" => "髮乳", -"发光可鉴" => "髮光可鑒", -"发匪" => "髮匪", -"发型" => "髮型", -"发夹" => "髮夾", -"发妻" => "髮妻", -"发姐" => "髮姐", -"发屋" => "髮屋", -"发带" => "髮帶", -"发廊" => "髮廊", -"发式" => "髮式", -"发引千钧" => "髮引千鈞", -"发指" => "髮指", -"发卷" => "髮捲", -"发根" => "髮根", -"发油" => "髮油", -"发漂" => "髮漂", -"发状" => "髮狀", -"发癣" => "髮癬", -"发短心长" => "髮短心長", -"发禁" => "髮禁", -"发笺" => "髮箋", -"发纱" => "髮紗", -"发结" => "髮結", -"发丝" => "髮絲", -"发网" => "髮網", -"发脚" => "髮腳", -"发肤" => "髮膚", -"发胶" => "髮膠", -"发菜" => "髮菜", -"发蜡" => "髮蠟", -"发踊冲冠" => "髮踴沖冠", -"发辫" => "髮辮", -"发针" => "髮針", -"发钗" => "髮釵", -"发长" => "髮長", -"发际" => "髮際", -"发霜" => "髮霜", -"发饰" => "髮飾", -"发髻" => "髮髻", -"发鬓" => "髮鬢", -"髼松" => "髼鬆", -"鬅松" => "鬅鬆", -"松一口气" => "鬆一口氣", -"松了" => "鬆了", -"松些" => "鬆些", -"松劲" => "鬆勁", -"松动" => "鬆動", -"松口" => "鬆口", -"松土" => "鬆土", -"松宽" => "鬆寬", -"松弛" => "鬆弛", -"松快" => "鬆快", -"松懈" => "鬆懈", -"松手" => "鬆手", -"松散" => "鬆散", -"松柔" => "鬆柔", -"松气" => "鬆氣", -"松浮" => "鬆浮", -"松绑" => "鬆綁", -"松紧" => "鬆緊", -"松缓" => "鬆緩", -"松脆" => "鬆脆", -"松脱" => "鬆脫", -"松蛋" => "鬆蛋", -"松起" => "鬆起", -"松软" => "鬆軟", -"松通" => "鬆通", -"松开" => "鬆開", -"松饼" => "鬆餅", -"松松" => "鬆鬆", -"鬈发" => "鬈髮", -"胡子" => "鬍子", -"胡梢" => "鬍梢", -"胡渣" => "鬍渣", -"胡髭" => "鬍髭", -"胡须" => "鬍鬚", -"鬒发" => "鬒髮", -"须根" => "鬚根", -"须毛" => "鬚毛", -"须生" => "鬚生", -"须眉" => "鬚眉", -"须发" => "鬚髮", -"须须" => "鬚鬚", -"须鲨" => "鬚鯊", -"须鲸" => "鬚鯨", -"鬓发" => "鬢髮", -"斗上" => "鬥上", -"斗不过" => "鬥不過", -"斗了" => "鬥了", -"斗来斗去" => "鬥來鬥去", -"斗倒" => "鬥倒", -"斗劲" => "鬥勁", -"斗口" => "鬥口", -"斗嘴" => "鬥嘴", -"斗士" => "鬥士", -"斗子" => "鬥子", -"斗弄" => "鬥弄", -"斗志" => "鬥志", -"斗成" => "鬥成", -"斗批改" => "鬥批改", -"斗技" => "鬥技", -"斗智" => "鬥智", -"斗殴" => "鬥毆", -"斗气" => "鬥氣", -"斗法" => "鬥法", -"斗争" => "鬥爭", -"斗牛" => "鬥牛", -"斗狠" => "鬥狠", -"斗眼" => "鬥眼", -"斗私批修" => "鬥私批修", -"斗笠" => "鬥笠", -"斗草" => "鬥草", -"斗着" => "鬥著", -"斗蟋蟀" => "鬥蟋蟀", -"斗起" => "鬥起", -"斗鸡" => "鬥雞", -"斗斗" => "鬥鬥", -"斗鱼" => "鬥魚", -"斗鹌鹑" => "鬥鵪鶉", -"闹着玩儿" => "鬧著玩兒", -"闹钟" => "鬧鐘", -"哄动" => "鬨動", -"哄堂" => "鬨堂", -"哄笑" => "鬨笑", -"郁伊" => "鬱伊", -"郁勃" => "鬱勃", -"郁卒" => "鬱卒", -"郁堙不偶" => "鬱堙不偶", -"郁塞" => "鬱塞", -"郁垒" => "鬱壘", -"郁律" => "鬱律", -"郁悒" => "鬱悒", -"郁闷" => "鬱悶", -"郁愤" => "鬱憤", -"郁抑" => "鬱抑", -"郁挹" => "鬱挹", -"郁江" => "鬱江", -"郁沉沉" => "鬱沉沉", -"郁泱" => "鬱泱", -"郁火" => "鬱火", -"郁热" => "鬱熱", -"郁燠" => "鬱燠", -"郁症" => "鬱症", -"郁积" => "鬱積", -"郁纡" => "鬱紆", -"郁结" => "鬱結", -"郁蒸" => "鬱蒸", -"郁蓊" => "鬱蓊", -"郁血" => "鬱血", -"郁邑" => "鬱邑", -"郁郁" => "鬱郁", -"郁金" => "鬱金", -"郁金香" => "鬱金香", -"郁闭" => "鬱閉", -"郁陶" => "鬱陶", -"郁郁不平" => "鬱鬱不平", -"郁郁不乐" => "鬱鬱不樂", -"郁郁寡欢" => "鬱鬱寡歡", -"郁郁而终" => "鬱鬱而終", -"郁郁葱葱" => "鬱鬱蔥蔥", -"郁黑" => "鬱黑", -"魏征" => "魏徵", -"鱼干" => "魚乾", -"鱼松" => "魚鬆", -"鲸须" => "鯨鬚", -"鲇鱼" => "鯰魚", -"鸠占鹊巢" => "鳩佔鵲巢", -"凤梨干" => "鳳梨乾", -"鸣钟" => "鳴鐘", -"鸿范" => "鴻範", -"鹄发" => "鵠髮", -"雕悍" => "鵰悍", -"鹤发" => "鶴髮", -"咸味" => "鹹味", -"咸嘴淡舌" => "鹹嘴淡舌", -"咸土" => "鹹土", -"咸度" => "鹹度", -"咸得" => "鹹得", -"咸批" => "鹹批", -"咸水" => "鹹水", -"咸派" => "鹹派", -"咸海" => "鹹海", -"咸淡" => "鹹淡", -"咸湖" => "鹹湖", -"咸汤" => "鹹湯", -"咸潟" => "鹹潟", -"咸的" => "鹹的", -"咸粥" => "鹹粥", -"咸肉" => "鹹肉", -"咸菜" => "鹹菜", -"咸蛋" => "鹹蛋", -"咸猪肉" => "鹹豬肉", -"咸类" => "鹹類", -"咸食" => "鹹食", -"咸鱼" => "鹹魚", -"咸鸭蛋" => "鹹鴨蛋", -"咸卤" => "鹹鹵", -"咸咸" => "鹹鹹", -"硷化" => "鹼化", -"硷土金属" => "鹼土金屬", -"硷地" => "鹼地", -"硷度" => "鹼度", -"硷性" => "鹼性", -"硷水" => "鹼水", -"硷液" => "鹼液", -"硷熔" => "鹼熔", -"硷石灰" => "鹼石灰", -"硷纤维素" => "鹼纖維素", -"硷金属" => "鹼金屬", -"硷类" => "鹼類", -"盐打怎么咸" => "鹽打怎麼鹹", -"盐卤" => "鹽滷", -"盐余" => "鹽餘", -"盐硷土" => "鹽鹼土", -"盐硷滩" => "鹽鹼灘", -"丽于" => "麗於", -"麴霉" => "麴黴", -"面人儿" => "麵人兒", -"面价" => "麵價", -"面包" => "麵包", -"面团" => "麵團", -"面坊" => "麵坊", -"面坯儿" => "麵坯兒", -"面塑" => "麵塑", -"面店" => "麵店", -"面厂" => "麵廠", -"面杖" => "麵杖", -"面条" => "麵條", -"面汤" => "麵湯", -"面浆" => "麵漿", -"面灰" => "麵灰", -"面疙瘩" => "麵疙瘩", -"面皮" => "麵皮", -"面码儿" => "麵碼兒", -"面筋" => "麵筋", -"面粉" => "麵粉", -"面糊" => "麵糊", -"面线" => "麵線", -"面缸" => "麵缸", -"面茶" => "麵茶", -"面食" => "麵食", -"面饺" => "麵餃", -"面饼" => "麵餅", -"面馆" => "麵館", -"麻药" => "麻藥", -"麻醉药" => "麻醉藥", -"麻酱面" => "麻醬麵", -"麻雀在后" => "麻雀在後", -"黄干黑廋" => "黃乾黑廋", -"黄历" => "黃曆", -"黄钟" => "黃鐘", -"黄雀在后" => "黃雀在後", -"黄发垂髫" => "黃髮垂髫", -"黑奴吁天录" => "黑奴籲天錄", -"黑发" => "黑髮", -"点钟" => "點鐘", -"霉毒" => "黴毒", -"霉菌" => "黴菌", -"霉黑" => "黴黑", -"霉黧" => "黴黧", -"鼓里" => "鼓裡", -"冬冬" => "鼕鼕", -"鼠药" => "鼠藥", -"鼻梁" => "鼻樑", -"鼻准" => "鼻準", -"齐王舍牛" => "齊王捨牛", -"齿危发秀" => "齒危髮秀", -"齿发" => "齒髮", -"出剧" => "齣劇", -"出卡通" => "齣卡通", -"出戏" => "齣戲", -"出电影" => "齣電影", -"龙卷" => "龍捲", -"龙争虎斗" => "龍爭虎鬥", -"龙眼干" => "龍眼乾", -"龙虎斗" => "龍虎鬥", -"龙须" => "龍鬚", -"龟山庄" => "龜山庄", -"手塚治虫" => "手塚治虫", -"校仇" => "校讎", -"仇校" => "讎校", -"仇夷" => "讎夷", -"仇問" => "讎問", -"無言不仇" => "無言不讎", -"視如寇仇" => "視如寇讎", -"往日無仇" => "往日無讎", -"近日無仇" => "近日無讎", -"李連杰" => "李連杰", -"周杰倫" => "周杰倫", -"寶曆" => "寶曆", -"涂謹申" => "涂謹申", -"於姓" => "於姓", -"於氏" => "於氏", -"於夫羅" => "於夫羅", -"於梨華" => "於梨華", -"鄭凱云" => "鄭凱云", -"筑陽" => "筑陽", -"筑後" => "筑後", -"采石磯" => "采石磯", -"采石之戰" => "采石之戰", -"張三丰" => "張三丰", -"丰韻" => "丰韻", -"丰儀" => "丰儀", -"丰標不凡" => "丰標不凡", -"干細胞" => "幹細胞", -"干熱" => "乾熱", -"二里頭" => "二里頭", -"水里鄉" => "水里鄉", -"蒙胧" => "朦朧", -"酒曲" => "酒麴", -"呆里呆气" => "呆裡呆氣", -"拜托" => "拜託", -"委托书" => "委託書", -"委托" => "委託", -"挽詞" => "輓詞", -"挽聯" => "輓聯", -"挽詩" => "輓詩", -"於夫罗" => "於夫羅", -"府干預" => "府干預", -"府干擾" => "府干擾", +'㑩' => '儸', +'㓥' => '劏', +'㔉' => '劚', +'㖊' => '噚', +'㖞' => '喎', +'㛟' => '𡞵', +'㛠' => '𡢃', +'㛿' => '𡠹', +'㟆' => '㠏', +'㧑' => '撝', +'㧟' => '擓', +'㨫' => '㩜', +'㱩' => '殰', +'㱮' => '殨', +'㲿' => '瀇', +'㳠' => '澾', +'㶉' => '鸂', +'㶶' => '燶', +'㶽' => '煱', +'㺍' => '獱', +'㻏' => '𤫩', +'㻘' => '𤪺', +'䁖' => '瞜', +'䅉' => '稏', +'䇲' => '筴', +'䌶' => '䊷', +'䌷' => '紬', +'䌸' => '縳', +'䌹' => '絅', +'䌺' => '䋙', +'䌼' => '綐', +'䌽' => '綵', +'䌾' => '䋻', +'䍀' => '繿', +'䍁' => '繸', +'䑽' => '𦪙', +'䓕' => '薳', +'䗖' => '螮', +'䘛' => '𧝞', +'䙊' => '𧜵', +'䙓' => '襬', +'䜣' => '訢', +'䜥' => '𧩙', +'䜧' => '譅', +'䝙' => '貙', +'䞌' => '𧵳', +'䞍' => '䝼', +'䞐' => '賰', +'䢂' => '𨋢', +'䥺' => '釾', +'䥽' => '鏺', +'䥿' => '𨯅', +'䦀' => '𨦫', +'䦁' => '𨧜', +'䦃' => '鐯', +'䦅' => '鐥', +'䩄' => '靦', +'䭪' => '𩞯', +'䯃' => '𩣑', +'䯄' => '騧', +'䯅' => '䯀', +'䲝' => '䱽', +'䲞' => '𩶘', +'䲟' => '鮣', +'䲠' => '鰆', +'䲡' => '鰌', +'䲢' => '鰧', +'䲣' => '䱷', +'䴓' => '鳾', +'䴔' => '鵁', +'䴕' => '鴷', +'䴖' => '鶄', +'䴗' => '鶪', +'䴘' => '鷈', +'䴙' => '鷿', +'万' => '萬', +'与' => '與', +'专' => '專', +'业' => '業', +'丛' => '叢', +'东' => '東', +'丝' => '絲', +'丢' => '丟', +'两' => '兩', +'严' => '嚴', +'丧' => '喪', +'个' => '個', +'丰' => '豐', +'临' => '臨', +'为' => '為', +'丽' => '麗', +'举' => '舉', +'义' => '義', +'乌' => '烏', +'乐' => '樂', +'乔' => '喬', +'习' => '習', +'乡' => '鄉', +'书' => '書', +'买' => '買', +'乱' => '亂', +'争' => '爭', +'亏' => '虧', +'云' => '雲', +'亚' => '亞', +'产' => '產', +'亩' => '畝', +'亲' => '親', +'亵' => '褻', +'亸' => '嚲', +'亿' => '億', +'仅' => '僅', +'从' => '從', +'仑' => '侖', +'仓' => '倉', +'仪' => '儀', +'们' => '們', +'价' => '價', +'众' => '眾', +'优' => '優', +'会' => '會', +'伛' => '傴', +'伞' => '傘', +'伟' => '偉', +'传' => '傳', +'伣' => '俔', +'伤' => '傷', +'伥' => '倀', +'伦' => '倫', +'伧' => '傖', +'伪' => '偽', +'伫' => '佇', +'体' => '體', +'佥' => '僉', +'侠' => '俠', +'侣' => '侶', +'侥' => '僥', +'侦' => '偵', +'侧' => '側', +'侨' => '僑', +'侩' => '儈', +'侪' => '儕', +'侬' => '儂', +'俣' => '俁', +'俦' => '儔', +'俨' => '儼', +'俩' => '倆', +'俪' => '儷', +'俫' => '倈', +'俭' => '儉', +'债' => '債', +'倾' => '傾', +'偬' => '傯', +'偻' => '僂', +'偾' => '僨', +'偿' => '償', +'傥' => '儻', +'傧' => '儐', +'储' => '儲', +'傩' => '儺', +'儿' => '兒', +'兑' => '兌', +'兖' => '兗', +'党' => '黨', +'兰' => '蘭', +'关' => '關', +'兴' => '興', +'兹' => '茲', +'养' => '養', +'兽' => '獸', +'冁' => '囅', +'内' => '內', +'冈' => '岡', +'册' => '冊', +'写' => '寫', +'军' => '軍', +'农' => '農', +'冯' => '馮', +'冲' => '沖', +'决' => '決', +'况' => '況', +'冻' => '凍', +'净' => '凈', +'凉' => '涼', +'减' => '減', +'凑' => '湊', +'凛' => '凜', +'几' => '幾', +'凤' => '鳳', +'凫' => '鳧', +'凭' => '憑', +'凯' => '凱', +'击' => '擊', +'凿' => '鑿', +'刍' => '芻', +'刘' => '劉', +'则' => '則', +'刚' => '剛', +'创' => '創', +'删' => '刪', +'别' => '別', +'刬' => '剗', +'刭' => '剄', +'刹' => '剎', +'刽' => '劊', +'刿' => '劌', +'剀' => '剴', +'剂' => '劑', +'剐' => '剮', +'剑' => '劍', +'剥' => '剝', +'剧' => '劇', +'劝' => '勸', +'办' => '辦', +'务' => '務', +'劢' => '勱', +'动' => '動', +'励' => '勵', +'劲' => '勁', +'劳' => '勞', +'势' => '勢', +'勋' => '勛', +'勚' => '勩', +'匀' => '勻', +'匦' => '匭', +'匮' => '匱', +'区' => '區', +'医' => '醫', +'华' => '華', +'协' => '協', +'单' => '單', +'卖' => '賣', +'卢' => '盧', +'卤' => '鹵', +'卫' => '衛', +'却' => '卻', +'厂' => '廠', +'厅' => '廳', +'历' => '歷', +'厉' => '厲', +'压' => '壓', +'厌' => '厭', +'厍' => '厙', +'厐' => '龎', +'厕' => '廁', +'厢' => '廂', +'厣' => '厴', +'厦' => '廈', +'厨' => '廚', +'厩' => '廄', +'厮' => '廝', +'县' => '縣', +'叁' => '叄', +'参' => '參', +'双' => '雙', +'发' => '發', +'变' => '變', +'叙' => '敘', +'叠' => '疊', +'叶' => '葉', +'号' => '號', +'叹' => '嘆', +'叽' => '嘰', +'吓' => '嚇', +'吕' => '呂', +'吗' => '嗎', +'吣' => '唚', +'吨' => '噸', +'听' => '聽', +'启' => '啟', +'吴' => '吳', +'呐' => '吶', +'呒' => '嘸', +'呓' => '囈', +'呕' => '嘔', +'呖' => '嚦', +'呗' => '唄', +'员' => '員', +'呙' => '咼', +'呛' => '嗆', +'呜' => '嗚', +'咏' => '詠', +'咙' => '嚨', +'咛' => '嚀', +'咝' => '噝', +'咤' => '吒', +'响' => '響', +'哑' => '啞', +'哒' => '噠', +'哓' => '嘵', +'哔' => '嗶', +'哕' => '噦', +'哗' => '嘩', +'哙' => '噲', +'哜' => '嚌', +'哝' => '噥', +'哟' => '喲', +'唛' => '嘜', +'唝' => '嗊', +'唠' => '嘮', +'唡' => '啢', +'唢' => '嗩', +'唤' => '喚', +'啧' => '嘖', +'啬' => '嗇', +'啭' => '囀', +'啮' => '嚙', +'啴' => '嘽', +'啸' => '嘯', +'喷' => '噴', +'喽' => '嘍', +'喾' => '嚳', +'嗫' => '囁', +'嗳' => '噯', +'嘘' => '噓', +'嘤' => '嚶', +'嘱' => '囑', +'噜' => '嚕', +'嚣' => '囂', +'团' => '團', +'园' => '園', +'囱' => '囪', +'围' => '圍', +'囵' => '圇', +'国' => '國', +'图' => '圖', +'圆' => '圓', +'圣' => '聖', +'圹' => '壙', +'场' => '場', +'坏' => '壞', +'块' => '塊', +'坚' => '堅', +'坛' => '壇', +'坜' => '壢', +'坝' => '壩', +'坞' => '塢', +'坟' => '墳', +'坠' => '墜', +'垄' => '壟', +'垅' => '壠', +'垆' => '壚', +'垒' => '壘', +'垦' => '墾', +'垩' => '堊', +'垫' => '墊', +'垭' => '埡', +'垱' => '壋', +'垲' => '塏', +'垴' => '堖', +'埘' => '塒', +'埙' => '塤', +'埚' => '堝', +'埯' => '垵', +'堑' => '塹', +'堕' => '墮', +'墙' => '牆', +'壮' => '壯', +'声' => '聲', +'壳' => '殼', +'壶' => '壺', +'壸' => '壼', +'处' => '處', +'备' => '備', +'复' => '復', +'够' => '夠', +'头' => '頭', +'夸' => '誇', +'夹' => '夾', +'夺' => '奪', +'奁' => '奩', +'奂' => '奐', +'奋' => '奮', +'奖' => '獎', +'奥' => '奧', +'妆' => '妝', +'妇' => '婦', +'妈' => '媽', +'妩' => '嫵', +'妪' => '嫗', +'妫' => '媯', +'姗' => '姍', +'姹' => '奼', +'娄' => '婁', +'娅' => '婭', +'娆' => '嬈', +'娇' => '嬌', +'娈' => '孌', +'娱' => '娛', +'娲' => '媧', +'娴' => '嫻', +'婳' => '嫿', +'婴' => '嬰', +'婵' => '嬋', +'婶' => '嬸', +'媪' => '媼', +'嫒' => '嬡', +'嫔' => '嬪', +'嫱' => '嬙', +'嬷' => '嬤', +'孙' => '孫', +'学' => '學', +'孪' => '孿', +'宁' => '寧', +'宝' => '寶', +'实' => '實', +'宠' => '寵', +'审' => '審', +'宪' => '憲', +'宫' => '宮', +'宽' => '寬', +'宾' => '賓', +'寝' => '寢', +'对' => '對', +'寻' => '尋', +'导' => '導', +'寿' => '壽', +'将' => '將', +'尔' => '爾', +'尘' => '塵', +'尝' => '嘗', +'尧' => '堯', +'尴' => '尷', +'尸' => '屍', +'尽' => '盡', +'层' => '層', +'屃' => '屓', +'屉' => '屜', +'届' => '屆', +'属' => '屬', +'屡' => '屢', +'屦' => '屨', +'屿' => '嶼', +'岁' => '歲', +'岂' => '豈', +'岖' => '嶇', +'岗' => '崗', +'岘' => '峴', +'岙' => '嶴', +'岚' => '嵐', +'岛' => '島', +'岭' => '嶺', +'岽' => '崬', +'岿' => '巋', +'峄' => '嶧', +'峡' => '峽', +'峣' => '嶢', +'峤' => '嶠', +'峥' => '崢', +'峦' => '巒', +'崂' => '嶗', +'崃' => '崍', +'崄' => '嶮', +'崭' => '嶄', +'嵘' => '嶸', +'嵚' => '嶔', +'嵝' => '嶁', +'巅' => '巔', +'巩' => '鞏', +'巯' => '巰', +'币' => '幣', +'帅' => '帥', +'师' => '師', +'帏' => '幃', +'帐' => '帳', +'帘' => '簾', +'帜' => '幟', +'带' => '帶', +'帧' => '幀', +'帮' => '幫', +'帱' => '幬', +'帻' => '幘', +'帼' => '幗', +'幂' => '冪', +'并' => '並', +'广' => '廣', +'庆' => '慶', +'庐' => '廬', +'庑' => '廡', +'库' => '庫', +'应' => '應', +'庙' => '廟', +'庞' => '龐', +'废' => '廢', +'廪' => '廩', +'开' => '開', +'异' => '異', +'弃' => '棄', +'弑' => '弒', +'张' => '張', +'弥' => '彌', +'弪' => '弳', +'弯' => '彎', +'弹' => '彈', +'强' => '強', +'归' => '歸', +'当' => '當', +'录' => '錄', +'彦' => '彥', +'彻' => '徹', +'径' => '徑', +'徕' => '徠', +'忆' => '憶', +'忏' => '懺', +'忧' => '憂', +'忾' => '愾', +'怀' => '懷', +'态' => '態', +'怂' => '慫', +'怃' => '憮', +'怄' => '慪', +'怅' => '悵', +'怆' => '愴', +'怜' => '憐', +'总' => '總', +'怼' => '懟', +'怿' => '懌', +'恋' => '戀', +'恒' => '恆', +'恳' => '懇', +'恶' => '惡', +'恸' => '慟', +'恹' => '懨', +'恺' => '愷', +'恻' => '惻', +'恼' => '惱', +'恽' => '惲', +'悦' => '悅', +'悫' => '愨', +'悬' => '懸', +'悭' => '慳', +'悮' => '悞', +'悯' => '憫', +'惊' => '驚', +'惧' => '懼', +'惨' => '慘', +'惩' => '懲', +'惫' => '憊', +'惬' => '愜', +'惭' => '慚', +'惮' => '憚', +'惯' => '慣', +'愠' => '慍', +'愤' => '憤', +'愦' => '憒', +'愿' => '願', +'慑' => '懾', +'懑' => '懣', +'懒' => '懶', +'懔' => '懍', +'戆' => '戇', +'戋' => '戔', +'戏' => '戲', +'戗' => '戧', +'战' => '戰', +'戬' => '戩', +'戯' => '戱', +'户' => '戶', +'扑' => '撲', +'执' => '執', +'扩' => '擴', +'扪' => '捫', +'扫' => '掃', +'扬' => '揚', +'扰' => '擾', +'抚' => '撫', +'抛' => '拋', +'抟' => '摶', +'抠' => '摳', +'抡' => '掄', +'抢' => '搶', +'护' => '護', +'报' => '報', +'担' => '擔', +'拟' => '擬', +'拢' => '攏', +'拣' => '揀', +'拥' => '擁', +'拦' => '攔', +'拧' => '擰', +'拨' => '撥', +'择' => '擇', +'挂' => '掛', +'挚' => '摯', +'挛' => '攣', +'挜' => '掗', +'挝' => '撾', +'挞' => '撻', +'挟' => '挾', +'挠' => '撓', +'挡' => '擋', +'挢' => '撟', +'挣' => '掙', +'挤' => '擠', +'挥' => '揮', +'挦' => '撏', +'捝' => '挩', +'捞' => '撈', +'损' => '損', +'捡' => '撿', +'换' => '換', +'捣' => '搗', +'据' => '據', +'掳' => '擄', +'掴' => '摑', +'掷' => '擲', +'掸' => '撣', +'掺' => '摻', +'掼' => '摜', +'揽' => '攬', +'揾' => '搵', +'揿' => '撳', +'搀' => '攙', +'搁' => '擱', +'搂' => '摟', +'搅' => '攪', +'携' => '攜', +'摄' => '攝', +'摅' => '攄', +'摆' => '擺', +'摇' => '搖', +'摈' => '擯', +'摊' => '攤', +'撄' => '攖', +'撑' => '撐', +'撵' => '攆', +'撷' => '擷', +'撸' => '擼', +'撺' => '攛', +'擞' => '擻', +'攒' => '攢', +'敌' => '敵', +'敛' => '斂', +'数' => '數', +'斋' => '齋', +'斓' => '斕', +'斩' => '斬', +'断' => '斷', +'无' => '無', +'旧' => '舊', +'时' => '時', +'旷' => '曠', +'旸' => '暘', +'昙' => '曇', +'昼' => '晝', +'昽' => '曨', +'显' => '顯', +'晋' => '晉', +'晒' => '曬', +'晓' => '曉', +'晔' => '曄', +'晕' => '暈', +'晖' => '暉', +'暂' => '暫', +'暧' => '曖', +'术' => '術', +'机' => '機', +'杀' => '殺', +'杂' => '雜', +'权' => '權', +'杆' => '桿', +'条' => '條', +'来' => '來', +'杨' => '楊', +'杩' => '榪', +'杰' => '傑', +'极' => '極', +'构' => '構', +'枞' => '樅', +'枢' => '樞', +'枣' => '棗', +'枥' => '櫪', +'枧' => '梘', +'枨' => '棖', +'枪' => '槍', +'枫' => '楓', +'枭' => '梟', +'柜' => '櫃', +'柠' => '檸', +'柽' => '檉', +'栀' => '梔', +'栅' => '柵', +'标' => '標', +'栈' => '棧', +'栉' => '櫛', +'栊' => '櫳', +'栋' => '棟', +'栌' => '櫨', +'栎' => '櫟', +'栏' => '欄', +'树' => '樹', +'栖' => '棲', +'样' => '樣', +'栾' => '欒', +'桠' => '椏', +'桡' => '橈', +'桢' => '楨', +'档' => '檔', +'桤' => '榿', +'桥' => '橋', +'桦' => '樺', +'桧' => '檜', +'桨' => '槳', +'桩' => '樁', +'梦' => '夢', +'梼' => '檮', +'梾' => '棶', +'梿' => '槤', +'检' => '檢', +'棁' => '梲', +'棂' => '欞', +'椁' => '槨', +'椟' => '櫝', +'椠' => '槧', +'椤' => '欏', +'椭' => '橢', +'楼' => '樓', +'榄' => '欖', +'榅' => '榲', +'榇' => '櫬', +'榈' => '櫚', +'榉' => '櫸', +'槚' => '檟', +'槛' => '檻', +'槟' => '檳', +'槠' => '櫧', +'横' => '橫', +'樯' => '檣', +'樱' => '櫻', +'橥' => '櫫', +'橱' => '櫥', +'橹' => '櫓', +'橼' => '櫞', +'檩' => '檁', +'欢' => '歡', +'欤' => '歟', +'欧' => '歐', +'歼' => '殲', +'殁' => '歿', +'殇' => '殤', +'残' => '殘', +'殒' => '殞', +'殓' => '殮', +'殚' => '殫', +'殡' => '殯', +'殴' => '毆', +'毁' => '毀', +'毂' => '轂', +'毕' => '畢', +'毙' => '斃', +'毡' => '氈', +'毵' => '毿', +'氇' => '氌', +'气' => '氣', +'氢' => '氫', +'氩' => '氬', +'氲' => '氳', +'汇' => '匯', +'汉' => '漢', +'汤' => '湯', +'汹' => '洶', +'沟' => '溝', +'没' => '沒', +'沣' => '灃', +'沤' => '漚', +'沥' => '瀝', +'沦' => '淪', +'沧' => '滄', +'沩' => '溈', +'沪' => '滬', +'泞' => '濘', +'泪' => '淚', +'泶' => '澩', +'泷' => '瀧', +'泸' => '瀘', +'泺' => '濼', +'泻' => '瀉', +'泼' => '潑', +'泽' => '澤', +'泾' => '涇', +'洁' => '潔', +'洒' => '灑', +'洼' => '窪', +'浃' => '浹', +'浅' => '淺', +'浆' => '漿', +'浇' => '澆', +'浈' => '湞', +'浊' => '濁', +'测' => '測', +'浍' => '澮', +'济' => '濟', +'浏' => '瀏', +'浐' => '滻', +'浑' => '渾', +'浒' => '滸', +'浓' => '濃', +'浔' => '潯', +'涂' => '塗', +'涛' => '濤', +'涝' => '澇', +'涞' => '淶', +'涟' => '漣', +'涠' => '潿', +'涡' => '渦', +'涣' => '渙', +'涤' => '滌', +'润' => '潤', +'涧' => '澗', +'涨' => '漲', +'涩' => '澀', +'渊' => '淵', +'渌' => '淥', +'渍' => '漬', +'渎' => '瀆', +'渐' => '漸', +'渑' => '澠', +'渔' => '漁', +'渖' => '瀋', +'渗' => '滲', +'温' => '溫', +'湾' => '灣', +'湿' => '濕', +'溃' => '潰', +'溅' => '濺', +'溆' => '漵', +'滗' => '潷', +'滚' => '滾', +'滞' => '滯', +'滟' => '灧', +'滠' => '灄', +'满' => '滿', +'滢' => '瀅', +'滤' => '濾', +'滥' => '濫', +'滦' => '灤', +'滨' => '濱', +'滩' => '灘', +'滪' => '澦', +'漤' => '灠', +'潆' => '瀠', +'潇' => '瀟', +'潋' => '瀲', +'潍' => '濰', +'潜' => '潛', +'潴' => '瀦', +'澜' => '瀾', +'濑' => '瀨', +'濒' => '瀕', +'灏' => '灝', +'灭' => '滅', +'灯' => '燈', +'灵' => '靈', +'灾' => '災', +'灿' => '燦', +'炀' => '煬', +'炉' => '爐', +'炖' => '燉', +'炜' => '煒', +'炝' => '熗', +'点' => '點', +'炼' => '煉', +'炽' => '熾', +'烁' => '爍', +'烂' => '爛', +'烃' => '烴', +'烛' => '燭', +'烟' => '煙', +'烦' => '煩', +'烧' => '燒', +'烨' => '燁', +'烩' => '燴', +'烫' => '燙', +'烬' => '燼', +'热' => '熱', +'焕' => '煥', +'焖' => '燜', +'焘' => '燾', +'煴' => '熅', +'爱' => '愛', +'爷' => '爺', +'牍' => '牘', +'牦' => '氂', +'牵' => '牽', +'牺' => '犧', +'犊' => '犢', +'状' => '狀', +'犷' => '獷', +'犸' => '獁', +'犹' => '猶', +'狈' => '狽', +'狝' => '獮', +'狞' => '獰', +'独' => '獨', +'狭' => '狹', +'狮' => '獅', +'狯' => '獪', +'狰' => '猙', +'狱' => '獄', +'狲' => '猻', +'猃' => '獫', +'猎' => '獵', +'猕' => '獼', +'猡' => '玀', +'猪' => '豬', +'猫' => '貓', +'猬' => '蝟', +'献' => '獻', +'獭' => '獺', +'玑' => '璣', +'玚' => '瑒', +'玛' => '瑪', +'玮' => '瑋', +'环' => '環', +'现' => '現', +'玱' => '瑲', +'玺' => '璽', +'珐' => '琺', +'珑' => '瓏', +'珰' => '璫', +'珲' => '琿', +'琏' => '璉', +'琐' => '瑣', +'琼' => '瓊', +'瑶' => '瑤', +'瑷' => '璦', +'璎' => '瓔', +'瓒' => '瓚', +'瓯' => '甌', +'电' => '電', +'画' => '畫', +'畅' => '暢', +'畴' => '疇', +'疖' => '癤', +'疗' => '療', +'疟' => '瘧', +'疠' => '癘', +'疡' => '瘍', +'疬' => '癧', +'疭' => '瘲', +'疮' => '瘡', +'疯' => '瘋', +'疱' => '皰', +'疴' => '痾', +'痈' => '癰', +'痉' => '痙', +'痒' => '癢', +'痖' => '瘂', +'痨' => '癆', +'痪' => '瘓', +'痫' => '癇', +'瘅' => '癉', +'瘆' => '瘮', +'瘗' => '瘞', +'瘘' => '瘺', +'瘪' => '癟', +'瘫' => '癱', +'瘾' => '癮', +'瘿' => '癭', +'癞' => '癩', +'癣' => '癬', +'癫' => '癲', +'皑' => '皚', +'皱' => '皺', +'皲' => '皸', +'盏' => '盞', +'盐' => '鹽', +'监' => '監', +'盖' => '蓋', +'盗' => '盜', +'盘' => '盤', +'眍' => '瞘', +'眦' => '眥', +'眬' => '矓', +'睁' => '睜', +'睐' => '睞', +'睑' => '瞼', +'瞆' => '瞶', +'瞒' => '瞞', +'瞩' => '矚', +'矫' => '矯', +'矶' => '磯', +'矾' => '礬', +'矿' => '礦', +'砀' => '碭', +'码' => '碼', +'砖' => '磚', +'砗' => '硨', +'砚' => '硯', +'砜' => '碸', +'砺' => '礪', +'砻' => '礱', +'砾' => '礫', +'础' => '礎', +'硁' => '硜', +'硕' => '碩', +'硖' => '硤', +'硗' => '磽', +'硙' => '磑', +'确' => '確', +'硷' => '礆', +'碍' => '礙', +'碛' => '磧', +'碜' => '磣', +'碱' => '鹼', +'礼' => '禮', +'祃' => '禡', +'祎' => '禕', +'祢' => '禰', +'祯' => '禎', +'祷' => '禱', +'祸' => '禍', +'禀' => '稟', +'禄' => '祿', +'禅' => '禪', +'离' => '離', +'秃' => '禿', +'秆' => '稈', +'种' => '種', +'积' => '積', +'称' => '稱', +'秽' => '穢', +'秾' => '穠', +'稆' => '穭', +'税' => '稅', +'稣' => '穌', +'稳' => '穩', +'穑' => '穡', +'穷' => '窮', +'窃' => '竊', +'窍' => '竅', +'窎' => '窵', +'窑' => '窯', +'窜' => '竄', +'窝' => '窩', +'窥' => '窺', +'窦' => '竇', +'窭' => '窶', +'竖' => '豎', +'竞' => '競', +'笃' => '篤', +'笋' => '筍', +'笔' => '筆', +'笕' => '筧', +'笺' => '箋', +'笼' => '籠', +'笾' => '籩', +'筑' => '築', +'筚' => '篳', +'筛' => '篩', +'筜' => '簹', +'筝' => '箏', +'筹' => '籌', +'筼' => '篔', +'签' => '簽', +'简' => '簡', +'箓' => '籙', +'箦' => '簀', +'箧' => '篋', +'箨' => '籜', +'箩' => '籮', +'箪' => '簞', +'箫' => '簫', +'篑' => '簣', +'篓' => '簍', +'篮' => '籃', +'篱' => '籬', +'簖' => '籪', +'籁' => '籟', +'籴' => '糴', +'类' => '類', +'籼' => '秈', +'粜' => '糶', +'粝' => '糲', +'粤' => '粵', +'粪' => '糞', +'粮' => '糧', +'糁' => '糝', +'糇' => '餱', +'紧' => '緊', +'絷' => '縶', +'纟' => '糹', +'纠' => '糾', +'纡' => '紆', +'红' => '紅', +'纣' => '紂', +'纤' => '纖', +'纥' => '紇', +'约' => '約', +'级' => '級', +'纨' => '紈', +'纩' => '纊', +'纪' => '紀', +'纫' => '紉', +'纬' => '緯', +'纭' => '紜', +'纮' => '紘', +'纯' => '純', +'纰' => '紕', +'纱' => '紗', +'纲' => '綱', +'纳' => '納', +'纴' => '紝', +'纵' => '縱', +'纶' => '綸', +'纷' => '紛', +'纸' => '紙', +'纹' => '紋', +'纺' => '紡', +'纻' => '紵', +'纼' => '紖', +'纽' => '紐', +'纾' => '紓', +'线' => '線', +'绀' => '紺', +'绁' => '紲', +'绂' => '紱', +'练' => '練', +'组' => '組', +'绅' => '紳', +'细' => '細', +'织' => '織', +'终' => '終', +'绉' => '縐', +'绊' => '絆', +'绋' => '紼', +'绌' => '絀', +'绍' => '紹', +'绎' => '繹', +'经' => '經', +'绐' => '紿', +'绑' => '綁', +'绒' => '絨', +'结' => '結', +'绔' => '絝', +'绕' => '繞', +'绖' => '絰', +'绗' => '絎', +'绘' => '繪', +'给' => '給', +'绚' => '絢', +'绛' => '絳', +'络' => '絡', +'绝' => '絕', +'绞' => '絞', +'统' => '統', +'绠' => '綆', +'绡' => '綃', +'绢' => '絹', +'绣' => '綉', +'绤' => '綌', +'绥' => '綏', +'绦' => '絛', +'继' => '繼', +'绨' => '綈', +'绩' => '績', +'绪' => '緒', +'绫' => '綾', +'绬' => '緓', +'续' => '續', +'绮' => '綺', +'绯' => '緋', +'绰' => '綽', +'绱' => '緔', +'绲' => '緄', +'绳' => '繩', +'维' => '維', +'绵' => '綿', +'绶' => '綬', +'绷' => '綳', +'绸' => '綢', +'绹' => '綯', +'绺' => '綹', +'绻' => '綣', +'综' => '綜', +'绽' => '綻', +'绾' => '綰', +'绿' => '綠', +'缀' => '綴', +'缁' => '緇', +'缂' => '緙', +'缃' => '緗', +'缄' => '緘', +'缅' => '緬', +'缆' => '纜', +'缇' => '緹', +'缈' => '緲', +'缉' => '緝', +'缊' => '縕', +'缋' => '繢', +'缌' => '緦', +'缍' => '綞', +'缎' => '緞', +'缏' => '緶', +'缑' => '緱', +'缒' => '縋', +'缓' => '緩', +'缔' => '締', +'缕' => '縷', +'编' => '編', +'缗' => '緡', +'缘' => '緣', +'缙' => '縉', +'缚' => '縛', +'缛' => '縟', +'缜' => '縝', +'缝' => '縫', +'缞' => '縗', +'缟' => '縞', +'缠' => '纏', +'缡' => '縭', +'缢' => '縊', +'缣' => '縑', +'缤' => '繽', +'缥' => '縹', +'缦' => '縵', +'缧' => '縲', +'缨' => '纓', +'缩' => '縮', +'缪' => '繆', +'缫' => '繅', +'缬' => '纈', +'缭' => '繚', +'缮' => '繕', +'缯' => '繒', +'缰' => '韁', +'缱' => '繾', +'缲' => '繰', +'缳' => '繯', +'缴' => '繳', +'缵' => '纘', +'罂' => '罌', +'网' => '網', +'罗' => '羅', +'罚' => '罰', +'罢' => '罷', +'罴' => '羆', +'羁' => '羈', +'羟' => '羥', +'翘' => '翹', +'耢' => '耮', +'耧' => '耬', +'耸' => '聳', +'耻' => '恥', +'聂' => '聶', +'聋' => '聾', +'职' => '職', +'聍' => '聹', +'联' => '聯', +'聩' => '聵', +'聪' => '聰', +'肃' => '肅', +'肠' => '腸', +'肤' => '膚', +'肮' => '骯', +'肴' => '餚', +'肾' => '腎', +'肿' => '腫', +'胀' => '脹', +'胁' => '脅', +'胆' => '膽', +'胜' => '勝', +'胧' => '朧', +'胨' => '腖', +'胪' => '臚', +'胫' => '脛', +'胶' => '膠', +'脉' => '脈', +'脍' => '膾', +'脏' => '臟', +'脐' => '臍', +'脑' => '腦', +'脓' => '膿', +'脔' => '臠', +'脚' => '腳', +'脱' => '脫', +'脶' => '腡', +'脸' => '臉', +'腊' => '臘', +'腭' => '齶', +'腻' => '膩', +'腼' => '靦', +'腽' => '膃', +'腾' => '騰', +'膑' => '臏', +'臜' => '臢', +'舆' => '輿', +'舣' => '艤', +'舰' => '艦', +'舱' => '艙', +'舻' => '艫', +'艰' => '艱', +'艳' => '艷', +'艺' => '藝', +'节' => '節', +'芈' => '羋', +'芗' => '薌', +'芜' => '蕪', +'芦' => '蘆', +'苁' => '蓯', +'苇' => '葦', +'苈' => '藶', +'苋' => '莧', +'苌' => '萇', +'苍' => '蒼', +'苎' => '苧', +'苏' => '蘇', +'苧' => '苎', +'苹' => '蘋', +'茎' => '莖', +'茏' => '蘢', +'茑' => '蔦', +'茔' => '塋', +'茕' => '煢', +'茧' => '繭', +'荆' => '荊', +'荐' => '薦', +'荙' => '薘', +'荚' => '莢', +'荛' => '蕘', +'荜' => '蓽', +'荞' => '蕎', +'荟' => '薈', +'荠' => '薺', +'荡' => '盪', +'荣' => '榮', +'荤' => '葷', +'荥' => '滎', +'荦' => '犖', +'荧' => '熒', +'荨' => '蕁', +'荩' => '藎', +'荪' => '蓀', +'荫' => '蔭', +'荬' => '蕒', +'荭' => '葒', +'荮' => '葤', +'药' => '葯', +'莅' => '蒞', +'莱' => '萊', +'莲' => '蓮', +'莳' => '蒔', +'莴' => '萵', +'莶' => '薟', +'获' => '獲', +'莸' => '蕕', +'莹' => '瑩', +'莺' => '鶯', +'莼' => '蒓', +'萝' => '蘿', +'萤' => '螢', +'营' => '營', +'萦' => '縈', +'萧' => '蕭', +'萨' => '薩', +'葱' => '蔥', +'蒇' => '蕆', +'蒉' => '蕢', +'蒋' => '蔣', +'蒌' => '蔞', +'蓝' => '藍', +'蓟' => '薊', +'蓠' => '蘺', +'蓣' => '蕷', +'蓥' => '鎣', +'蓦' => '驀', +'蔂' => '虆', +'蔷' => '薔', +'蔹' => '蘞', +'蔺' => '藺', +'蔼' => '藹', +'蕰' => '薀', +'蕲' => '蘄', +'蕴' => '蘊', +'薮' => '藪', +'藓' => '蘚', +'蘖' => '櫱', +'虏' => '虜', +'虑' => '慮', +'虚' => '虛', +'虫' => '蟲', +'虬' => '虯', +'虮' => '蟣', +'虽' => '雖', +'虾' => '蝦', +'虿' => '蠆', +'蚀' => '蝕', +'蚁' => '蟻', +'蚂' => '螞', +'蚕' => '蠶', +'蚬' => '蜆', +'蛊' => '蠱', +'蛎' => '蠣', +'蛏' => '蟶', +'蛮' => '蠻', +'蛰' => '蟄', +'蛱' => '蛺', +'蛲' => '蟯', +'蛳' => '螄', +'蛴' => '蠐', +'蜕' => '蛻', +'蜗' => '蝸', +'蜡' => '蠟', +'蝇' => '蠅', +'蝈' => '蟈', +'蝉' => '蟬', +'蝼' => '螻', +'蝾' => '蠑', +'螀' => '螿', +'螨' => '蟎', +'蟏' => '蠨', +'衅' => '釁', +'衔' => '銜', +'补' => '補', +'衬' => '襯', +'衮' => '袞', +'袄' => '襖', +'袅' => '裊', +'袆' => '褘', +'袜' => '襪', +'袭' => '襲', +'袯' => '襏', +'装' => '裝', +'裆' => '襠', +'裈' => '褌', +'裢' => '褳', +'裣' => '襝', +'裤' => '褲', +'裥' => '襇', +'褛' => '褸', +'褴' => '襤', +'见' => '見', +'观' => '觀', +'觃' => '覎', +'规' => '規', +'觅' => '覓', +'视' => '視', +'觇' => '覘', +'览' => '覽', +'觉' => '覺', +'觊' => '覬', +'觋' => '覡', +'觌' => '覿', +'觍' => '覥', +'觎' => '覦', +'觏' => '覯', +'觐' => '覲', +'觑' => '覷', +'觞' => '觴', +'触' => '觸', +'觯' => '觶', +'訚' => '誾', +'誉' => '譽', +'誊' => '謄', +'讠' => '訁', +'计' => '計', +'订' => '訂', +'讣' => '訃', +'认' => '認', +'讥' => '譏', +'讦' => '訐', +'讧' => '訌', +'讨' => '討', +'让' => '讓', +'讪' => '訕', +'讫' => '訖', +'讬' => '託', +'训' => '訓', +'议' => '議', +'讯' => '訊', +'记' => '記', +'讱' => '訒', +'讲' => '講', +'讳' => '諱', +'讴' => '謳', +'讵' => '詎', +'讶' => '訝', +'讷' => '訥', +'许' => '許', +'讹' => '訛', +'论' => '論', +'讻' => '訩', +'讼' => '訟', +'讽' => '諷', +'设' => '設', +'访' => '訪', +'诀' => '訣', +'证' => '證', +'诂' => '詁', +'诃' => '訶', +'评' => '評', +'诅' => '詛', +'识' => '識', +'诇' => '詗', +'诈' => '詐', +'诉' => '訴', +'诊' => '診', +'诋' => '詆', +'诌' => '謅', +'词' => '詞', +'诎' => '詘', +'诏' => '詔', +'诐' => '詖', +'译' => '譯', +'诒' => '詒', +'诓' => '誆', +'诔' => '誄', +'试' => '試', +'诖' => '詿', +'诗' => '詩', +'诘' => '詰', +'诙' => '詼', +'诚' => '誠', +'诛' => '誅', +'诜' => '詵', +'话' => '話', +'诞' => '誕', +'诟' => '詬', +'诠' => '詮', +'诡' => '詭', +'询' => '詢', +'诣' => '詣', +'诤' => '諍', +'该' => '該', +'详' => '詳', +'诧' => '詫', +'诨' => '諢', +'诩' => '詡', +'诪' => '譸', +'诫' => '誡', +'诬' => '誣', +'语' => '語', +'诮' => '誚', +'误' => '誤', +'诰' => '誥', +'诱' => '誘', +'诲' => '誨', +'诳' => '誑', +'说' => '說', +'诵' => '誦', +'诶' => '誒', +'请' => '請', +'诸' => '諸', +'诹' => '諏', +'诺' => '諾', +'读' => '讀', +'诼' => '諑', +'诽' => '誹', +'课' => '課', +'诿' => '諉', +'谀' => '諛', +'谁' => '誰', +'谂' => '諗', +'调' => '調', +'谄' => '諂', +'谅' => '諒', +'谆' => '諄', +'谇' => '誶', +'谈' => '談', +'谊' => '誼', +'谋' => '謀', +'谌' => '諶', +'谍' => '諜', +'谎' => '謊', +'谏' => '諫', +'谐' => '諧', +'谑' => '謔', +'谒' => '謁', +'谓' => '謂', +'谔' => '諤', +'谕' => '諭', +'谖' => '諼', +'谗' => '讒', +'谘' => '諮', +'谙' => '諳', +'谚' => '諺', +'谛' => '諦', +'谜' => '謎', +'谝' => '諞', +'谞' => '諝', +'谟' => '謨', +'谠' => '讜', +'谡' => '謖', +'谢' => '謝', +'谣' => '謠', +'谤' => '謗', +'谥' => '謚', +'谦' => '謙', +'谧' => '謐', +'谨' => '謹', +'谩' => '謾', +'谪' => '謫', +'谫' => '譾', +'谬' => '謬', +'谭' => '譚', +'谮' => '譖', +'谯' => '譙', +'谰' => '讕', +'谱' => '譜', +'谲' => '譎', +'谳' => '讞', +'谴' => '譴', +'谵' => '譫', +'谶' => '讖', +'豮' => '豶', +'贝' => '貝', +'贞' => '貞', +'负' => '負', +'贠' => '貟', +'贡' => '貢', +'财' => '財', +'责' => '責', +'贤' => '賢', +'败' => '敗', +'账' => '賬', +'货' => '貨', +'质' => '質', +'贩' => '販', +'贪' => '貪', +'贫' => '貧', +'贬' => '貶', +'购' => '購', +'贮' => '貯', +'贯' => '貫', +'贰' => '貳', +'贱' => '賤', +'贲' => '賁', +'贳' => '貰', +'贴' => '貼', +'贵' => '貴', +'贶' => '貺', +'贷' => '貸', +'贸' => '貿', +'费' => '費', +'贺' => '賀', +'贻' => '貽', +'贼' => '賊', +'贽' => '贄', +'贾' => '賈', +'贿' => '賄', +'赀' => '貲', +'赁' => '賃', +'赂' => '賂', +'赃' => '贓', +'资' => '資', +'赅' => '賅', +'赆' => '贐', +'赇' => '賕', +'赈' => '賑', +'赉' => '賚', +'赊' => '賒', +'赋' => '賦', +'赌' => '賭', +'赍' => '齎', +'赎' => '贖', +'赏' => '賞', +'赐' => '賜', +'赑' => '贔', +'赒' => '賙', +'赓' => '賡', +'赔' => '賠', +'赕' => '賧', +'赖' => '賴', +'赗' => '賵', +'赘' => '贅', +'赙' => '賻', +'赚' => '賺', +'赛' => '賽', +'赜' => '賾', +'赝' => '贗', +'赞' => '贊', +'赟' => '贇', +'赠' => '贈', +'赡' => '贍', +'赢' => '贏', +'赣' => '贛', +'赪' => '赬', +'赵' => '趙', +'赶' => '趕', +'趋' => '趨', +'趱' => '趲', +'趸' => '躉', +'跃' => '躍', +'跄' => '蹌', +'跞' => '躒', +'践' => '踐', +'跶' => '躂', +'跷' => '蹺', +'跸' => '蹕', +'跹' => '躚', +'跻' => '躋', +'踊' => '踴', +'踌' => '躊', +'踪' => '蹤', +'踬' => '躓', +'踯' => '躑', +'蹑' => '躡', +'蹒' => '蹣', +'蹰' => '躕', +'蹿' => '躥', +'躏' => '躪', +'躜' => '躦', +'躯' => '軀', +'车' => '車', +'轧' => '軋', +'轨' => '軌', +'轩' => '軒', +'轪' => '軑', +'轫' => '軔', +'转' => '轉', +'轭' => '軛', +'轮' => '輪', +'软' => '軟', +'轰' => '轟', +'轱' => '軲', +'轲' => '軻', +'轳' => '轤', +'轴' => '軸', +'轵' => '軹', +'轶' => '軼', +'轷' => '軤', +'轸' => '軫', +'轹' => '轢', +'轺' => '軺', +'轻' => '輕', +'轼' => '軾', +'载' => '載', +'轾' => '輊', +'轿' => '轎', +'辀' => '輈', +'辁' => '輇', +'辂' => '輅', +'较' => '較', +'辄' => '輒', +'辅' => '輔', +'辆' => '輛', +'辇' => '輦', +'辈' => '輩', +'辉' => '輝', +'辊' => '輥', +'辋' => '輞', +'辌' => '輬', +'辍' => '輟', +'辎' => '輜', +'辏' => '輳', +'辐' => '輻', +'辑' => '輯', +'辒' => '轀', +'输' => '輸', +'辔' => '轡', +'辕' => '轅', +'辖' => '轄', +'辗' => '輾', +'辘' => '轆', +'辙' => '轍', +'辚' => '轔', +'辞' => '辭', +'辩' => '辯', +'辫' => '辮', +'边' => '邊', +'辽' => '遼', +'达' => '達', +'迁' => '遷', +'过' => '過', +'迈' => '邁', +'运' => '運', +'还' => '還', +'这' => '這', +'进' => '進', +'远' => '遠', +'违' => '違', +'连' => '連', +'迟' => '遲', +'迩' => '邇', +'迳' => '逕', +'迹' => '跡', +'适' => '適', +'选' => '選', +'逊' => '遜', +'递' => '遞', +'逦' => '邐', +'逻' => '邏', +'遗' => '遺', +'遥' => '遙', +'邓' => '鄧', +'邝' => '鄺', +'邬' => '鄔', +'邮' => '郵', +'邹' => '鄒', +'邺' => '鄴', +'邻' => '鄰', +'郏' => '郟', +'郐' => '鄶', +'郑' => '鄭', +'郓' => '鄆', +'郦' => '酈', +'郧' => '鄖', +'郸' => '鄲', +'酂' => '酇', +'酝' => '醞', +'酦' => '醱', +'酱' => '醬', +'酽' => '釅', +'酾' => '釃', +'酿' => '釀', +'释' => '釋', +'鉴' => '鑒', +'銮' => '鑾', +'錾' => '鏨', +'钅' => '釒', +'钆' => '釓', +'钇' => '釔', +'针' => '針', +'钉' => '釘', +'钊' => '釗', +'钋' => '釙', +'钌' => '釕', +'钍' => '釷', +'钎' => '釺', +'钏' => '釧', +'钐' => '釤', +'钑' => '鈒', +'钒' => '釩', +'钓' => '釣', +'钔' => '鍆', +'钕' => '釹', +'钖' => '鍚', +'钗' => '釵', +'钘' => '鈃', +'钙' => '鈣', +'钚' => '鈈', +'钛' => '鈦', +'钜' => '鉅', +'钝' => '鈍', +'钞' => '鈔', +'钟' => '鍾', +'钠' => '鈉', +'钡' => '鋇', +'钢' => '鋼', +'钣' => '鈑', +'钤' => '鈐', +'钥' => '鑰', +'钦' => '欽', +'钧' => '鈞', +'钨' => '鎢', +'钩' => '鉤', +'钪' => '鈧', +'钫' => '鈁', +'钬' => '鈥', +'钭' => '鈄', +'钮' => '鈕', +'钯' => '鈀', +'钰' => '鈺', +'钱' => '錢', +'钲' => '鉦', +'钳' => '鉗', +'钴' => '鈷', +'钵' => '缽', +'钶' => '鈳', +'钷' => '鉕', +'钸' => '鈽', +'钹' => '鈸', +'钺' => '鉞', +'钻' => '鑽', +'钼' => '鉬', +'钽' => '鉭', +'钾' => '鉀', +'钿' => '鈿', +'铀' => '鈾', +'铁' => '鐵', +'铂' => '鉑', +'铃' => '鈴', +'铄' => '鑠', +'铅' => '鉛', +'铆' => '鉚', +'铇' => '鉋', +'铈' => '鈰', +'铉' => '鉉', +'铊' => '鉈', +'铋' => '鉍', +'铌' => '鈮', +'铍' => '鈹', +'铎' => '鐸', +'铏' => '鉶', +'铐' => '銬', +'铑' => '銠', +'铒' => '鉺', +'铓' => '鋩', +'铔' => '錏', +'铕' => '銪', +'铖' => '鋮', +'铗' => '鋏', +'铘' => '鋣', +'铙' => '鐃', +'铚' => '銍', +'铛' => '鐺', +'铜' => '銅', +'铝' => '鋁', +'铞' => '銱', +'铟' => '銦', +'铠' => '鎧', +'铡' => '鍘', +'铢' => '銖', +'铣' => '銑', +'铤' => '鋌', +'铥' => '銩', +'铦' => '銛', +'铧' => '鏵', +'铨' => '銓', +'铩' => '鎩', +'铪' => '鉿', +'铫' => '銚', +'铬' => '鉻', +'铭' => '銘', +'铮' => '錚', +'铯' => '銫', +'铰' => '鉸', +'铱' => '銥', +'铲' => '鏟', +'铳' => '銃', +'铴' => '鐋', +'铵' => '銨', +'银' => '銀', +'铷' => '銣', +'铸' => '鑄', +'铹' => '鐒', +'铺' => '鋪', +'铻' => '鋙', +'铼' => '錸', +'铽' => '鋱', +'链' => '鏈', +'铿' => '鏗', +'销' => '銷', +'锁' => '鎖', +'锂' => '鋰', +'锃' => '鋥', +'锄' => '鋤', +'锅' => '鍋', +'锆' => '鋯', +'锇' => '鋨', +'锈' => '銹', +'锉' => '銼', +'锊' => '鋝', +'锋' => '鋒', +'锌' => '鋅', +'锍' => '鋶', +'锎' => '鐦', +'锏' => '鐧', +'锐' => '銳', +'锑' => '銻', +'锒' => '鋃', +'锓' => '鋟', +'锔' => '鋦', +'锕' => '錒', +'锖' => '錆', +'锗' => '鍺', +'锘' => '鍩', +'错' => '錯', +'锚' => '錨', +'锛' => '錛', +'锜' => '錡', +'锝' => '鍀', +'锞' => '錁', +'锟' => '錕', +'锠' => '錩', +'锡' => '錫', +'锢' => '錮', +'锣' => '鑼', +'锤' => '錘', +'锥' => '錐', +'锦' => '錦', +'锧' => '鑕', +'锨' => '杴', +'锩' => '錈', +'锪' => '鍃', +'锫' => '錇', +'锬' => '錟', +'锭' => '錠', +'键' => '鍵', +'锯' => '鋸', +'锰' => '錳', +'锱' => '錙', +'锲' => '鍥', +'锳' => '鍈', +'锴' => '鍇', +'锵' => '鏘', +'锶' => '鍶', +'锷' => '鍔', +'锸' => '鍤', +'锹' => '鍬', +'锺' => '鍾', +'锻' => '鍛', +'锼' => '鎪', +'锽' => '鍠', +'锾' => '鍰', +'锿' => '鎄', +'镀' => '鍍', +'镁' => '鎂', +'镂' => '鏤', +'镃' => '鎡', +'镄' => '鐨', +'镅' => '鎇', +'镆' => '鏌', +'镇' => '鎮', +'镈' => '鎛', +'镉' => '鎘', +'镊' => '鑷', +'镋' => '鎲', +'镌' => '鐫', +'镍' => '鎳', +'镎' => '鎿', +'镏' => '鎦', +'镐' => '鎬', +'镑' => '鎊', +'镒' => '鎰', +'镓' => '鎵', +'镔' => '鑌', +'镕' => '鎔', +'镖' => '鏢', +'镗' => '鏜', +'镘' => '鏝', +'镙' => '鏍', +'镚' => '鏰', +'镛' => '鏞', +'镜' => '鏡', +'镝' => '鏑', +'镞' => '鏃', +'镟' => '鏇', +'镠' => '鏐', +'镡' => '鐔', +'镢' => '钁', +'镣' => '鐐', +'镤' => '鏷', +'镥' => '鑥', +'镦' => '鐓', +'镧' => '鑭', +'镨' => '鐠', +'镩' => '鑹', +'镪' => '鏹', +'镫' => '鐙', +'镬' => '鑊', +'镭' => '鐳', +'镮' => '鐶', +'镯' => '鐲', +'镰' => '鐮', +'镱' => '鐿', +'镲' => '鑔', +'镳' => '鑣', +'镴' => '鑞', +'镵' => '鑱', +'镶' => '鑲', +'长' => '長', +'门' => '門', +'闩' => '閂', +'闪' => '閃', +'闫' => '閆', +'闬' => '閈', +'闭' => '閉', +'问' => '問', +'闯' => '闖', +'闰' => '閏', +'闱' => '闈', +'闲' => '閑', +'闳' => '閎', +'间' => '間', +'闵' => '閔', +'闶' => '閌', +'闷' => '悶', +'闸' => '閘', +'闹' => '鬧', +'闺' => '閨', +'闻' => '聞', +'闼' => '闥', +'闽' => '閩', +'闾' => '閭', +'闿' => '闓', +'阀' => '閥', +'阁' => '閣', +'阂' => '閡', +'阃' => '閫', +'阄' => '鬮', +'阅' => '閱', +'阆' => '閬', +'阇' => '闍', +'阈' => '閾', +'阉' => '閹', +'阊' => '閶', +'阋' => '鬩', +'阌' => '閿', +'阍' => '閽', +'阎' => '閻', +'阏' => '閼', +'阐' => '闡', +'阑' => '闌', +'阒' => '闃', +'阓' => '闠', +'阔' => '闊', +'阕' => '闋', +'阖' => '闔', +'阗' => '闐', +'阘' => '闒', +'阙' => '闕', +'阚' => '闞', +'阛' => '闤', +'队' => '隊', +'阳' => '陽', +'阴' => '陰', +'阵' => '陣', +'阶' => '階', +'际' => '際', +'陆' => '陸', +'陇' => '隴', +'陈' => '陳', +'陉' => '陘', +'陕' => '陝', +'陧' => '隉', +'陨' => '隕', +'险' => '險', +'随' => '隨', +'隐' => '隱', +'隶' => '隸', +'隽' => '雋', +'难' => '難', +'雏' => '雛', +'雠' => '讎', +'雳' => '靂', +'雾' => '霧', +'霁' => '霽', +'霡' => '霢', +'霭' => '靄', +'靓' => '靚', +'静' => '靜', +'靥' => '靨', +'鞑' => '韃', +'鞒' => '鞽', +'鞯' => '韉', +'韦' => '韋', +'韧' => '韌', +'韨' => '韍', +'韩' => '韓', +'韪' => '韙', +'韫' => '韞', +'韬' => '韜', +'韵' => '韻', +'页' => '頁', +'顶' => '頂', +'顷' => '頃', +'顸' => '頇', +'项' => '項', +'顺' => '順', +'须' => '須', +'顼' => '頊', +'顽' => '頑', +'顾' => '顧', +'顿' => '頓', +'颀' => '頎', +'颁' => '頒', +'颂' => '頌', +'颃' => '頏', +'预' => '預', +'颅' => '顱', +'领' => '領', +'颇' => '頗', +'颈' => '頸', +'颉' => '頡', +'颊' => '頰', +'颋' => '頲', +'颌' => '頜', +'颍' => '潁', +'颎' => '熲', +'颏' => '頦', +'颐' => '頤', +'频' => '頻', +'颒' => '頮', +'颓' => '頹', +'颔' => '頷', +'颕' => '頴', +'颖' => '穎', +'颗' => '顆', +'题' => '題', +'颙' => '顒', +'颚' => '顎', +'颛' => '顓', +'颜' => '顏', +'额' => '額', +'颞' => '顳', +'颟' => '顢', +'颠' => '顛', +'颡' => '顙', +'颢' => '顥', +'颤' => '顫', +'颥' => '顬', +'颦' => '顰', +'颧' => '顴', +'风' => '風', +'飏' => '颺', +'飐' => '颭', +'飑' => '颮', +'飒' => '颯', +'飓' => '颶', +'飔' => '颸', +'飕' => '颼', +'飖' => '颻', +'飗' => '飀', +'飘' => '飄', +'飙' => '飆', +'飚' => '飈', +'飞' => '飛', +'飨' => '饗', +'餍' => '饜', +'饣' => '飠', +'饤' => '飣', +'饥' => '飢', +'饦' => '飥', +'饧' => '餳', +'饨' => '飩', +'饩' => '餼', +'饪' => '飪', +'饫' => '飫', +'饬' => '飭', +'饭' => '飯', +'饮' => '飲', +'饯' => '餞', +'饰' => '飾', +'饱' => '飽', +'饲' => '飼', +'饳' => '飿', +'饴' => '飴', +'饵' => '餌', +'饶' => '饒', +'饷' => '餉', +'饸' => '餄', +'饹' => '餎', +'饺' => '餃', +'饻' => '餏', +'饼' => '餅', +'饽' => '餑', +'饾' => '餖', +'饿' => '餓', +'馀' => '餘', +'馁' => '餒', +'馂' => '餕', +'馃' => '餜', +'馄' => '餛', +'馅' => '餡', +'馆' => '館', +'馇' => '餷', +'馈' => '饋', +'馉' => '餶', +'馊' => '餿', +'馋' => '饞', +'馌' => '饁', +'馍' => '饃', +'馎' => '餺', +'馏' => '餾', +'馐' => '饈', +'馑' => '饉', +'馒' => '饅', +'馓' => '饊', +'馔' => '饌', +'馕' => '饢', +'马' => '馬', +'驭' => '馭', +'驮' => '馱', +'驯' => '馴', +'驰' => '馳', +'驱' => '驅', +'驲' => '馹', +'驳' => '駁', +'驴' => '驢', +'驵' => '駔', +'驶' => '駛', +'驷' => '駟', +'驸' => '駙', +'驹' => '駒', +'驺' => '騶', +'驻' => '駐', +'驼' => '駝', +'驽' => '駑', +'驾' => '駕', +'驿' => '驛', +'骀' => '駘', +'骁' => '驍', +'骂' => '罵', +'骃' => '駰', +'骄' => '驕', +'骅' => '驊', +'骆' => '駱', +'骇' => '駭', +'骈' => '駢', +'骉' => '驫', +'骊' => '驪', +'骋' => '騁', +'验' => '驗', +'骍' => '騂', +'骎' => '駸', +'骏' => '駿', +'骐' => '騏', +'骑' => '騎', +'骒' => '騍', +'骓' => '騅', +'骔' => '騌', +'骕' => '驌', +'骖' => '驂', +'骗' => '騙', +'骘' => '騭', +'骙' => '騤', +'骚' => '騷', +'骛' => '騖', +'骜' => '驁', +'骝' => '騮', +'骞' => '騫', +'骟' => '騸', +'骠' => '驃', +'骡' => '騾', +'骢' => '驄', +'骣' => '驏', +'骤' => '驟', +'骥' => '驥', +'骦' => '驦', +'骧' => '驤', +'髅' => '髏', +'髋' => '髖', +'髌' => '髕', +'鬓' => '鬢', +'魇' => '魘', +'魉' => '魎', +'鱼' => '魚', +'鱽' => '魛', +'鱾' => '魢', +'鱿' => '魷', +'鲀' => '魨', +'鲁' => '魯', +'鲂' => '魴', +'鲃' => '䰾', +'鲄' => '魺', +'鲅' => '鮁', +'鲆' => '鮃', +'鲇' => '鯰', +'鲈' => '鱸', +'鲉' => '鮋', +'鲊' => '鮓', +'鲋' => '鮒', +'鲌' => '鮊', +'鲍' => '鮑', +'鲎' => '鱟', +'鲏' => '鮍', +'鲐' => '鮐', +'鲑' => '鮭', +'鲒' => '鮚', +'鲓' => '鮳', +'鲔' => '鮪', +'鲕' => '鮞', +'鲖' => '鮦', +'鲗' => '鰂', +'鲘' => '鮜', +'鲙' => '鱠', +'鲚' => '鱭', +'鲛' => '鮫', +'鲜' => '鮮', +'鲝' => '鮺', +'鲞' => '鯗', +'鲟' => '鱘', +'鲠' => '鯁', +'鲡' => '鱺', +'鲢' => '鰱', +'鲣' => '鰹', +'鲤' => '鯉', +'鲥' => '鰣', +'鲦' => '鰷', +'鲧' => '鯀', +'鲨' => '鯊', +'鲩' => '鯇', +'鲪' => '鮶', +'鲫' => '鯽', +'鲬' => '鯒', +'鲭' => '鯖', +'鲮' => '鯪', +'鲯' => '鯕', +'鲰' => '鯫', +'鲱' => '鯡', +'鲲' => '鯤', +'鲳' => '鯧', +'鲴' => '鯝', +'鲵' => '鯢', +'鲶' => '鯰', +'鲷' => '鯛', +'鲸' => '鯨', +'鲹' => '鰺', +'鲺' => '鯴', +'鲻' => '鯔', +'鲼' => '鱝', +'鲽' => '鰈', +'鲾' => '鰏', +'鲿' => '鱨', +'鳀' => '鯷', +'鳁' => '鰮', +'鳂' => '鰃', +'鳃' => '鰓', +'鳄' => '鱷', +'鳅' => '鰍', +'鳆' => '鰒', +'鳇' => '鰉', +'鳈' => '鰁', +'鳉' => '鱂', +'鳊' => '鯿', +'鳋' => '鰠', +'鳌' => '鰲', +'鳍' => '鰭', +'鳎' => '鰨', +'鳏' => '鰥', +'鳐' => '鰩', +'鳑' => '鰟', +'鳒' => '鰜', +'鳓' => '鰳', +'鳔' => '鰾', +'鳕' => '鱈', +'鳖' => '鱉', +'鳗' => '鰻', +'鳘' => '鰵', +'鳙' => '鱅', +'鳚' => '䲁', +'鳛' => '鰼', +'鳜' => '鱖', +'鳝' => '鱔', +'鳞' => '鱗', +'鳟' => '鱒', +'鳠' => '鱯', +'鳡' => '鱤', +'鳢' => '鱧', +'鳣' => '鱣', +'鸟' => '鳥', +'鸠' => '鳩', +'鸡' => '雞', +'鸢' => '鳶', +'鸣' => '鳴', +'鸤' => '鳲', +'鸥' => '鷗', +'鸦' => '鴉', +'鸧' => '鶬', +'鸨' => '鴇', +'鸩' => '鴆', +'鸪' => '鴣', +'鸫' => '鶇', +'鸬' => '鸕', +'鸭' => '鴨', +'鸮' => '鴞', +'鸯' => '鴦', +'鸰' => '鴒', +'鸱' => '鴟', +'鸲' => '鴝', +'鸳' => '鴛', +'鸴' => '鷽', +'鸵' => '鴕', +'鸶' => '鷥', +'鸷' => '鷙', +'鸸' => '鴯', +'鸹' => '鴰', +'鸺' => '鵂', +'鸻' => '鴴', +'鸼' => '鵃', +'鸽' => '鴿', +'鸾' => '鸞', +'鸿' => '鴻', +'鹀' => '鵐', +'鹁' => '鵓', +'鹂' => '鸝', +'鹃' => '鵑', +'鹄' => '鵠', +'鹅' => '鵝', +'鹆' => '鵒', +'鹇' => '鷳', +'鹈' => '鵜', +'鹉' => '鵡', +'鹊' => '鵲', +'鹋' => '鶓', +'鹌' => '鵪', +'鹍' => '鵾', +'鹎' => '鵯', +'鹏' => '鵬', +'鹐' => '鵮', +'鹑' => '鶉', +'鹒' => '鶊', +'鹓' => '鵷', +'鹔' => '鷫', +'鹕' => '鶘', +'鹖' => '鶡', +'鹗' => '鶚', +'鹘' => '鶻', +'鹙' => '鶖', +'鹚' => '鶿', +'鹛' => '鶥', +'鹜' => '鶩', +'鹝' => '鷊', +'鹞' => '鷂', +'鹟' => '鶲', +'鹠' => '鶹', +'鹡' => '鶺', +'鹢' => '鷁', +'鹣' => '鶼', +'鹤' => '鶴', +'鹥' => '鷖', +'鹦' => '鸚', +'鹧' => '鷓', +'鹨' => '鷚', +'鹩' => '鷯', +'鹪' => '鷦', +'鹫' => '鷲', +'鹬' => '鷸', +'鹭' => '鷺', +'鹯' => '鸇', +'鹰' => '鷹', +'鹱' => '鸌', +'鹲' => '鸏', +'鹳' => '鸛', +'鹴' => '鸘', +'鹾' => '鹺', +'麦' => '麥', +'麸' => '麩', +'黄' => '黃', +'黉' => '黌', +'黡' => '黶', +'黩' => '黷', +'黪' => '黲', +'黾' => '黽', +'鼋' => '黿', +'鼍' => '鼉', +'鼗' => '鞀', +'鼹' => '鼴', +'齐' => '齊', +'齑' => '齏', +'齿' => '齒', +'龀' => '齔', +'龁' => '齕', +'龂' => '齗', +'龃' => '齟', +'龄' => '齡', +'龅' => '齙', +'龆' => '齠', +'龇' => '齜', +'龈' => '齦', +'龉' => '齬', +'龊' => '齪', +'龋' => '齲', +'龌' => '齷', +'龙' => '龍', +'龚' => '龔', +'龛' => '龕', +'龟' => '龜', +'𠮶' => '嗰', +'𡒄' => '壈', +'𦈖' => '䌈', +'𨰾' => '鎷', +'𨰿' => '釳', +'𨱀' => '𨥛', +'𨱁' => '鈠', +'𨱂' => '鈋', +'𨱃' => '鈲', +'𨱄' => '鈯', +'𨱅' => '鉁', +'𨱇' => '銶', +'𨱈' => '鋉', +'𨱉' => '鍄', +'𨱊' => '𨧱', +'𨱋' => '錂', +'𨱌' => '鏆', +'𨱍' => '鎯', +'𨱎' => '鍮', +'𨱏' => '鎝', +'𨱐' => '𨫒', +'𨱒' => '鏉', +'𨱓' => '鐎', +'𨱔' => '鐏', +'𨱕' => '𨮂', +'𨸂' => '閍', +'𨸃' => '閐', +'𩏼' => '䪏', +'𩏽' => '𩏪', +'𩏾' => '𩎢', +'𩏿' => '䪘', +'𩐀' => '䪗', +'𩖕' => '𩓣', +'𩖖' => '顃', +'𩖗' => '䫴', +'𩙥' => '颰', +'𩙦' => '𩗀', +'𩙧' => '𩗡', +'𩙨' => '𩘹', +'𩙩' => '𩘀', +'𩙪' => '颷', +'𩙫' => '颾', +'𩙬' => '𩘺', +'𩙭' => '𩘝', +'𩙮' => '䬘', +'𩙯' => '䬝', +'𩙰' => '𩙈', +'𩠅' => '𩟐', +'𩠆' => '𩜦', +'𩠇' => '䭀', +'𩠈' => '䭃', +'𩠋' => '𩝔', +'𩠌' => '餸', +'𩧦' => '𩡺', +'𩧨' => '駎', +'𩧩' => '𩤊', +'𩧪' => '䮾', +'𩧫' => '駚', +'𩧬' => '𩢡', +'𩧭' => '䭿', +'𩧮' => '𩢾', +'𩧯' => '驋', +'𩧰' => '䮝', +'𩧱' => '𩥉', +'𩧲' => '駧', +'𩧳' => '𩢸', +'𩧴' => '駩', +'𩧵' => '𩢴', +'𩧶' => '𩣏', +'𩧺' => '駶', +'𩧻' => '𩣵', +'𩧼' => '𩣺', +'𩧿' => '䮠', +'𩨀' => '騔', +'𩨁' => '䮞', +'𩨃' => '騝', +'𩨄' => '騪', +'𩨅' => '𩤸', +'𩨆' => '𩤙', +'𩨈' => '騟', +'𩨉' => '𩤲', +'𩨊' => '騚', +'𩨋' => '𩥄', +'𩨌' => '𩥑', +'𩨍' => '𩥇', +'𩨏' => '䮳', +'𩨐' => '𩧆', +'𩽹' => '魥', +'𩽺' => '𩵩', +'𩽻' => '𩵹', +'𩽼' => '鯶', +'𩽽' => '𩶱', +'𩽾' => '鮟', +'𩽿' => '𩶰', +'𩾀' => '鮕', +'𩾁' => '鯄', +'𩾃' => '鮸', +'𩾄' => '𩷰', +'𩾅' => '𩸃', +'𩾆' => '𩸦', +'𩾇' => '鯱', +'𩾈' => '䱙', +'𩾊' => '䱬', +'𩾋' => '䱰', +'𩾌' => '鱇', +'𩾎' => '𩽇', +'𪉂' => '䲰', +'𪉃' => '鳼', +'𪉄' => '𩿪', +'𪉅' => '𪀦', +'𪉆' => '鴲', +'𪉈' => '鴜', +'𪉉' => '𪁈', +'𪉊' => '鷨', +'𪉋' => '𪀾', +'𪉌' => '𪁖', +'𪉍' => '鵚', +'𪉎' => '𪂆', +'𪉏' => '𪃏', +'𪉐' => '𪃍', +'𪉑' => '鷔', +'𪉒' => '𪄕', +'𪉔' => '𪄆', +'𪉕' => '𪇳', +'𪎈' => '䴬', +'𪎉' => '麲', +'𪎊' => '麨', +'𪎋' => '䴴', +'𪎌' => '麳', +'𪚏' => '𪘀', +'𪚐' => '𪘯', +'' => '棡', +'0多只' => '0多隻', +'0天后' => '0天後', +'0只' => '0隻', +'0余' => '0餘', +'1天后' => '1天後', +'1只' => '1隻', +'2天后' => '2天後', +'2只' => '2隻', +'3天后' => '3天後', +'3只' => '3隻', +'4天后' => '4天後', +'4只' => '4隻', +'5天后' => '5天後', +'5只' => '5隻', +'6天后' => '6天後', +'6只' => '6隻', +'7天后' => '7天後', +'7只' => '7隻', +'8天后' => '8天後', +'8只' => '8隻', +'9天后' => '9天後', +'9只' => '9隻', +'〇只' => '〇隻', +'〇余' => '〇餘', +'一干二净' => '一乾二淨', +'一伙人' => '一伙人', +'一伙头' => '一伙頭', +'一伙食' => '一伙食', +'一并' => '一併', +'一个准' => '一個準', +'一前一后' => '一前一後', +'一划' => '一劃', +'一半只' => '一半只', +'一口钟' => '一口鐘', +'一吊钱' => '一吊錢', +'一地里' => '一地裡', +'一伙' => '一夥', +'一天后' => '一天後', +'一干人' => '一干人', +'一干家中' => '一干家中', +'一干弟兄' => '一干弟兄', +'一干弟子' => '一干弟子', +'一干部下' => '一干部下', +'一吊' => '一弔', +'一别头' => '一彆頭', +'一斗斗' => '一斗斗', +'一树百获' => '一樹百穫', +'一准' => '一準', +'一争两丑' => '一爭兩醜', +'一物克一物' => '一物剋一物', +'一目了然' => '一目了然', +'一扎' => '一紮', +'一冲' => '一衝', +'一锅面' => '一鍋麵', +'一只' => '一隻', +'一面食' => '一面食', +'一发千钧' => '一髮千鈞', +'一哄而散' => '一鬨而散', +'丁丁当当' => '丁丁當當', +'丁丑' => '丁丑', +'七划' => '七劃', +'七天后' => '七天後', +'七情六欲' => '七情六慾', +'七扎' => '七紮', +'七只' => '七隻', +'万俟' => '万俟', +'万旗' => '万旗', +'三天后' => '三天後', +'三征七辟' => '三徵七辟', +'三准' => '三準', +'三扎' => '三紮', +'三统历' => '三統曆', +'三统历史' => '三統歷史', +'三复' => '三複', +'三只' => '三隻', +'三余' => '三餘', +'上吊自杀' => '上吊自殺', +'上吊' => '上弔', +'上梁山' => '上梁山', +'上梁' => '上樑', +'上签名' => '上簽名', +'上签字' => '上簽字', +'上签写' => '上簽寫', +'上签收' => '上簽收', +'上签' => '上籤', +'上药' => '上藥', +'上面糊' => '上面糊', +'下仑路' => '下崙路', +'下于' => '下於', +'下梁' => '下樑', +'下注解' => '下注解', +'下签' => '下籤', +'下药' => '下藥', +'不干不净' => '不乾不淨', +'不占' => '不佔', +'不克自制' => '不克自制', +'不准他' => '不准他', +'不准你' => '不准你', +'不准她' => '不准她', +'不准它' => '不准它', +'不准我' => '不准我', +'不准没' => '不准沒', +'不准翻印' => '不准翻印', +'不准许' => '不准許', +'不准谁' => '不准誰', +'不前不后' => '不前不後', +'不加自制' => '不加自制', +'不占凶吉' => '不占凶吉', +'不占卜' => '不占卜', +'不占吉凶' => '不占吉凶', +'不占算' => '不占算', +'不好干涉' => '不好干涉', +'不好干预' => '不好干預', +'不好干預' => '不好干預', +'不嫌母丑' => '不嫌母醜', +'不寒而栗' => '不寒而慄', +'不干事' => '不干事', +'不干他' => '不干他', +'不干休' => '不干休', +'不干你' => '不干你', +'不干她' => '不干她', +'不干它' => '不干它', +'不干我' => '不干我', +'不干擾' => '不干擾', +'不干扰' => '不干擾', +'不干涉' => '不干涉', +'不干牠' => '不干牠', +'不干犯' => '不干犯', +'不干预' => '不干預', +'不干預' => '不干預', +'不干' => '不幹', +'不吊' => '不弔', +'不采' => '不採', +'不斗胆' => '不斗膽', +'不断发' => '不斷發', +'不每只' => '不每只', +'不准' => '不準', +'不准确' => '不準確', +'不谷' => '不穀', +'不药而愈' => '不藥而癒', +'不托' => '不託', +'不负所托' => '不負所托', +'不通吊庆' => '不通弔慶', +'不丑' => '不醜', +'不采声' => '不采聲', +'不锈钢' => '不鏽鋼', +'不食干腊' => '不食乾腊', +'不斗' => '不鬥', +'丑三' => '丑三', +'丑婆子' => '丑婆子', +'丑年' => '丑年', +'丑日' => '丑日', +'丑旦' => '丑旦', +'丑时' => '丑時', +'丑月' => '丑月', +'丑表功' => '丑表功', +'丑角' => '丑角', +'且于' => '且於', +'世田谷' => '世田谷', +'世界杯' => '世界盃', +'丢丑' => '丟醜', +'并不准' => '並不准', +'并存着' => '並存著', +'并于' => '並於', +'并发动' => '並發動', +'并发展' => '並發展', +'并发现' => '並發現', +'并发表' => '並發表', +'中国国际信托投资公司' => '中國國際信托投資公司', +'中国烟草总公司' => '中國烟草總公司', +'中仑' => '中崙', +'中岳' => '中嶽', +'中于' => '中於', +'中签' => '中籤', +'中美发表' => '中美發表', +'中药' => '中藥', +'丰儀' => '丰儀', +'丰仪' => '丰儀', +'丰南' => '丰南', +'丰台' => '丰台', +'丰姿' => '丰姿', +'丰容' => '丰容', +'丰度' => '丰度', +'丰情' => '丰情', +'丰标' => '丰標', +'丰标不凡' => '丰標不凡', +'丰標不凡' => '丰標不凡', +'丰神' => '丰神', +'丰茸' => '丰茸', +'丰采' => '丰采', +'丰韵' => '丰韻', +'丰韻' => '丰韻', +'丸药' => '丸藥', +'丹药' => '丹藥', +'主仆' => '主僕', +'主干' => '主幹', +'么么小丑' => '么麼小丑', +'之一只' => '之一只', +'之二只' => '之二只', +'之八九只' => '之八九只', +'之后' => '之後', +'之征' => '之徵', +'之于' => '之於', +'之托' => '之託', +'之余' => '之餘', +'乙丑' => '乙丑', +'九世之仇' => '九世之讎', +'九划' => '九劃', +'九天后' => '九天後', +'九谷' => '九穀', +'九扎' => '九紮', +'九只' => '九隻', +'也斗了胆' => '也斗了膽', +'干干' => '乾乾', +'干干儿的' => '乾乾兒的', +'干干净净' => '乾乾淨淨', +'干井' => '乾井', +'干个够' => '乾個夠', +'干儿' => '乾兒', +'干冰' => '乾冰', +'干冷' => '乾冷', +'干刻版' => '乾刻版', +'干剥剥' => '乾剝剝', +'干卦' => '乾卦', +'干吊着下巴' => '乾吊著下巴', +'干和' => '乾和', +'干咳' => '乾咳', +'干咽' => '乾咽', +'干哥' => '乾哥', +'干哭' => '乾哭', +'干唱' => '乾唱', +'干啼' => '乾啼', +'干乔' => '乾喬', +'干呕' => '乾嘔', +'干哕' => '乾噦', +'干嚎' => '乾嚎', +'干回付' => '乾回付', +'干圆洁净' => '乾圓潔淨', +'干地' => '乾地', +'干坤' => '乾坤', +'干坞' => '乾塢', +'干女' => '乾女', +'干奴才' => '乾奴才', +'干妹' => '乾妹', +'干姊' => '乾姊', +'干娘' => '乾娘', +'干妈' => '乾媽', +'干子' => '乾子', +'干季' => '乾季', +'干尸' => '乾屍', +'干屎橛' => '乾屎橛', +'干巴' => '乾巴', +'干式' => '乾式', +'干弟' => '乾弟', +'干急' => '乾急', +'干性' => '乾性', +'干打雷' => '乾打雷', +'干折' => '乾折', +'干撂台' => '乾撂台', +'干撇下' => '乾撇下', +'干擦' => '乾擦', +'干支剌' => '乾支剌', +'干支支' => '乾支支', +'干敲梆子不卖油' => '乾敲梆子不賣油', +'干料' => '乾料', +'干旱' => '乾旱', +'干暖' => '乾暖', +'干材' => '乾材', +'干村沙' => '乾村沙', +'干杯' => '乾杯', +'干果' => '乾果', +'干枯' => '乾枯', +'干柴' => '乾柴', +'干柴烈火' => '乾柴烈火', +'干梅' => '乾梅', +'干死' => '乾死', +'干池' => '乾池', +'干没' => '乾沒', +'干洗' => '乾洗', +'干涸' => '乾涸', +'干凉' => '乾涼', +'干净' => '乾淨', +'干渠' => '乾渠', +'干渴' => '乾渴', +'干沟' => '乾溝', +'干漆' => '乾漆', +'干涩' => '乾澀', +'干湿' => '乾濕', +'干熬' => '乾熬', +'干热' => '乾熱', +'干熱' => '乾熱', +'干灯盏' => '乾燈盞', +'干燥' => '乾燥', +'干爸' => '乾爸', +'干爹' => '乾爹', +'干爽' => '乾爽', +'干片' => '乾片', +'干生受' => '乾生受', +'干生子' => '乾生子', +'干产' => '乾產', +'干田' => '乾田', +'干疥' => '乾疥', +'干瘦' => '乾瘦', +'干瘪' => '乾癟', +'干癣' => '乾癬', +'干瘾' => '乾癮', +'干白儿' => '乾白兒', +'干的' => '乾的', +'干眼' => '乾眼', +'干瞪眼' => '乾瞪眼', +'干礼' => '乾禮', +'干稿' => '乾稿', +'干笑' => '乾笑', +'干等' => '乾等', +'干篾片' => '乾篾片', +'干粉' => '乾粉', +'干粮' => '乾糧', +'干结' => '乾結', +'干丝' => '乾絲', +'干纲' => '乾綱', +'干绷' => '乾繃', +'干耗' => '乾耗', +'干肉片' => '乾肉片', +'干股' => '乾股', +'干肥' => '乾肥', +'干脆' => '乾脆', +'干花' => '乾花', +'干刍' => '乾芻', +'干苔' => '乾苔', +'干茨腊' => '乾茨臘', +'干茶钱' => '乾茶錢', +'干草' => '乾草', +'干菜' => '乾菜', +'干落' => '乾落', +'干着' => '乾著', +'干姜' => '乾薑', +'干薪' => '乾薪', +'干虔' => '乾虔', +'干号' => '乾號', +'干血浆' => '乾血漿', +'干衣' => '乾衣', +'干裂' => '乾裂', +'干亲' => '乾親', +'乾象历' => '乾象曆', +'乾象曆' => '乾象曆', +'干贝' => '乾貝', +'干货' => '乾貨', +'干躁' => '乾躁', +'干逼' => '乾逼', +'干酪' => '乾酪', +'干酵母' => '乾酵母', +'干醋' => '乾醋', +'干量' => '乾量', +'干阿奶' => '乾阿奶', +'干隆' => '乾隆', +'干雷' => '乾雷', +'干电' => '乾電', +'干霍乱' => '乾霍亂', +'干颡' => '乾顙', +'干台' => '乾颱', +'干饭' => '乾飯', +'干馆' => '乾館', +'干糇' => '乾餱', +'干馏' => '乾餾', +'干鱼' => '乾魚', +'干鲜' => '乾鮮', +'干面' => '乾麵', +'乱发' => '亂髮', +'乱哄' => '亂鬨', +'乱哄不过来' => '亂鬨不過來', +'事后' => '事後', +'事情干脆' => '事情干脆', +'事有斗巧' => '事有鬥巧', +'事迹' => '事迹', +'事都干脆' => '事都干脆', +'二不棱登' => '二不稜登', +'二划' => '二劃', +'二只得' => '二只得', +'二天后' => '二天後', +'二仑' => '二崙', +'二缶钟惑' => '二缶鐘惑', +'二老板' => '二老板', +'二虎相斗' => '二虎相鬥', +'二里头' => '二里頭', +'二里頭' => '二里頭', +'二只' => '二隻', +'于美人' => '于美人', +'云乎' => '云乎', +'云云' => '云云', +'云何' => '云何', +'云为' => '云為', +'云然' => '云然', +'云尔' => '云爾', +'互于' => '互於', +'五划' => '五劃', +'五天后' => '五天後', +'五岳' => '五嶽', +'五谷' => '五穀', +'五扎' => '五紮', +'五行生克' => '五行生剋', +'五谷王北街' => '五谷王北街', +'五谷王南街' => '五谷王南街', +'五只' => '五隻', +'五出' => '五齣', +'井干摧败' => '井榦摧敗', +'井里' => '井裡', +'亚于' => '亞於', +'交于' => '交於', +'交托' => '交託', +'交游' => '交遊', +'交哄' => '交鬨', +'亦云' => '亦云', +'亦于' => '亦於', +'亦庄亦谐' => '亦莊亦諧', +'亮丑' => '亮醜', +'亮钟' => '亮鐘', +'人云' => '人云', +'人参加' => '人參加', +'人参展' => '人參展', +'人参战' => '人參戰', +'人参拜' => '人參拜', +'人参政' => '人參政', +'人参照' => '人參照', +'人参看' => '人參看', +'人参禅' => '人參禪', +'人参考' => '人參考', +'人参与' => '人參與', +'人参见' => '人參見', +'人参观' => '人參觀', +'人参谋' => '人參謀', +'人参议' => '人參議', +'人参赞' => '人參贊', +'人参透' => '人參透', +'人参选' => '人參選', +'人参酌' => '人參酌', +'人参阅' => '人參閱', +'人口分布' => '人口分布', +'人后' => '人後', +'人欲' => '人慾', +'人物志' => '人物誌', +'人参' => '人蔘', +'什锦面' => '什錦麵', +'什么' => '什麼', +'仇仇' => '仇讎', +'今后' => '今後', +'介于' => '介於', +'付托' => '付託', +'仙后座' => '仙后座', +'仙药' => '仙藥', +'令人发指' => '令人髮指', +'以后' => '以後', +'以自制' => '以自制', +'仰药' => '仰藥', +'任教于' => '任教於', +'任于' => '任於', +'仿制' => '仿製', +'企划' => '企劃', +'伊府面' => '伊府麵', +'伊斯兰教历' => '伊斯蘭教曆', +'伊斯兰教历史' => '伊斯蘭教歷史', +'伊斯兰历' => '伊斯蘭曆', +'伊斯兰历史' => '伊斯蘭歷史', +'伊郁' => '伊鬱', +'伏几' => '伏几', +'伐罪吊民' => '伐罪弔民', +'休征' => '休徵', +'伙头' => '伙頭', +'伴游' => '伴遊', +'似于' => '似於', +'但云' => '但云', +'布于' => '佈於', +'布道' => '佈道', +'位于' => '位於', +'位准' => '位準', +'低于' => '低於', +'低洼' => '低洼', +'住扎' => '住紮', +'占0' => '佔0', +'占1' => '佔1', +'占2' => '佔2', +'占3' => '佔3', +'占4' => '佔4', +'占5' => '佔5', +'占6' => '佔6', +'占7' => '佔7', +'占8' => '佔8', +'占9' => '佔9', +'占A' => '佔A', +'占B' => '佔B', +'占C' => '佔C', +'占D' => '佔D', +'占E' => '佔E', +'占F' => '佔F', +'占G' => '佔G', +'占H' => '佔H', +'占I' => '佔I', +'占J' => '佔J', +'占K' => '佔K', +'占L' => '佔L', +'占M' => '佔M', +'占N' => '佔N', +'占O' => '佔O', +'占P' => '佔P', +'占Q' => '佔Q', +'占R' => '佔R', +'占S' => '佔S', +'占T' => '佔T', +'占U' => '佔U', +'占V' => '佔V', +'占W' => '佔W', +'占X' => '佔X', +'占Y' => '佔Y', +'占Z' => '佔Z', +'占a' => '佔a', +'占b' => '佔b', +'占c' => '佔c', +'占d' => '佔d', +'占e' => '佔e', +'占f' => '佔f', +'占g' => '佔g', +'占h' => '佔h', +'占i' => '佔i', +'占j' => '佔j', +'占k' => '佔k', +'占l' => '佔l', +'占m' => '佔m', +'占n' => '佔n', +'占o' => '佔o', +'占p' => '佔p', +'占q' => '佔q', +'占r' => '佔r', +'占s' => '佔s', +'占t' => '佔t', +'占u' => '佔u', +'占v' => '佔v', +'占w' => '佔w', +'占x' => '佔x', +'占y' => '佔y', +'占z' => '佔z', +'占〇' => '佔〇', +'占一' => '佔一', +'占七' => '佔七', +'占万' => '佔万', +'占三' => '佔三', +'占上风' => '佔上風', +'占下' => '佔下', +'占下风' => '佔下風', +'占不占' => '佔不佔', +'占不足' => '佔不足', +'占世界' => '佔世界', +'占中' => '佔中', +'占主' => '佔主', +'占九' => '佔九', +'占了' => '佔了', +'占二' => '佔二', +'占五' => '佔五', +'占人便宜' => '佔人便宜', +'占位' => '佔位', +'占住' => '佔住', +'占占' => '佔佔', +'占便宜' => '佔便宜', +'占俄' => '佔俄', +'占个' => '佔個', +'占个位' => '佔個位', +'占停车' => '佔停車', +'占亿' => '佔億', +'占优' => '佔優', +'占先' => '佔先', +'占光' => '佔光', +'占全' => '佔全', +'占两' => '佔兩', +'占八' => '佔八', +'占六' => '佔六', +'占分' => '佔分', +'占到' => '佔到', +'占加' => '佔加', +'占劣' => '佔劣', +'占北' => '佔北', +'占十' => '佔十', +'占千' => '佔千', +'占半' => '佔半', +'占南' => '佔南', +'占印' => '佔印', +'占去' => '佔去', +'占取' => '佔取', +'占台' => '佔台', +'占哺乳' => '佔哺乳', +'占嗫' => '佔囁', +'占四' => '佔四', +'占国内' => '佔國內', +'占在' => '佔在', +'占地' => '佔地', +'占场' => '佔場', +'占压' => '佔壓', +'占多' => '佔多', +'占大' => '佔大', +'占好' => '佔好', +'占小' => '佔小', +'占少' => '佔少', +'占局部' => '佔局部', +'占屋' => '佔屋', +'占山' => '佔山', +'占市场' => '佔市場', +'占平均' => '佔平均', +'占床' => '佔床', +'占座' => '佔座', +'占后' => '佔後', +'占得' => '佔得', +'占德' => '佔德', +'占掉' => '佔掉', +'占据' => '佔據', +'占整体' => '佔整體', +'占新' => '佔新', +'占有' => '佔有', +'占有欲' => '佔有慾', +'占东' => '佔東', +'占查' => '佔查', +'占次' => '佔次', +'占比' => '佔比', +'占法' => '佔法', +'占满' => '佔滿', +'占澳' => '佔澳', +'占为' => '佔為', +'占率' => '佔率', +'占用' => '佔用', +'占毕' => '佔畢', +'占百' => '佔百', +'占尽' => '佔盡', +'占稳' => '佔穩', +'占网' => '佔網', +'占线' => '佔線', +'占总' => '佔總', +'占缺' => '佔缺', +'占美' => '佔美', +'占耕' => '佔耕', +'占至多' => '佔至多', +'占至少' => '佔至少', +'占英' => '佔英', +'占着' => '佔著', +'占葡' => '佔葡', +'占苏' => '佔蘇', +'占西' => '佔西', +'占资源' => '佔資源', +'占起' => '佔起', +'占超过' => '佔超過', +'占过' => '佔過', +'占道' => '佔道', +'占零' => '佔零', +'占领' => '佔領', +'占头' => '佔頭', +'占头筹' => '佔頭籌', +'占饭' => '佔飯', +'占香' => '佔香', +'占马' => '佔馬', +'占高枝儿' => '佔高枝兒', +'占0' => '佔0', +'占1' => '佔1', +'占2' => '佔2', +'占3' => '佔3', +'占4' => '佔4', +'占5' => '佔5', +'占6' => '佔6', +'占7' => '佔7', +'占8' => '佔8', +'占9' => '佔9', +'占A' => '佔A', +'占B' => '佔B', +'占C' => '佔C', +'占D' => '佔D', +'占E' => '佔E', +'占F' => '佔F', +'占G' => '佔G', +'占H' => '佔H', +'占I' => '佔I', +'占J' => '佔J', +'占K' => '佔K', +'占L' => '佔L', +'占M' => '佔M', +'占N' => '佔N', +'占O' => '佔O', +'占P' => '佔P', +'占Q' => '佔Q', +'占R' => '佔R', +'占S' => '佔S', +'占T' => '佔T', +'占U' => '佔U', +'占V' => '佔V', +'占W' => '佔W', +'占X' => '佔X', +'占Y' => '佔Y', +'占Z' => '佔Z', +'占a' => '佔a', +'占b' => '佔b', +'占c' => '佔c', +'占d' => '佔d', +'占e' => '佔e', +'占f' => '佔f', +'占g' => '佔g', +'占h' => '佔h', +'占i' => '佔i', +'占j' => '佔j', +'占k' => '佔k', +'占l' => '佔l', +'占m' => '佔m', +'占n' => '佔n', +'占o' => '佔o', +'占p' => '佔p', +'占q' => '佔q', +'占r' => '佔r', +'占s' => '佔s', +'占t' => '佔t', +'占u' => '佔u', +'占v' => '佔v', +'占w' => '佔w', +'占x' => '佔x', +'占y' => '佔y', +'占z' => '佔z', +'余光中' => '余光中', +'余光生' => '余光生', +'佛罗棱萨' => '佛羅稜薩', +'作奸犯科' => '作姦犯科', +'作准' => '作準', +'作庄' => '作莊', +'你斗了胆' => '你斗了膽', +'你才子发昏' => '你纔子發昏', +'佣金收益' => '佣金收益', +'佣金费用' => '佣金費用', +'佳肴' => '佳肴', +'并一不二' => '併一不二', +'并入' => '併入', +'并兼' => '併兼', +'并到' => '併到', +'并合' => '併合', +'并名' => '併名', +'并吞下' => '併吞下', +'并拢' => '併攏', +'并案' => '併案', +'并流' => '併流', +'并火' => '併火', +'并为' => '併為', +'并产' => '併產', +'并当' => '併當', +'并叠' => '併疊', +'并发' => '併發', +'并科' => '併科', +'并网' => '併網', +'并线' => '併線', +'并肩子' => '併肩子', +'并购' => '併購', +'并除' => '併除', +'并骨' => '併骨', +'来于' => '來於', +'来复' => '來複', +'侍仆' => '侍僕', +'供制' => '供製', +'依依不舍' => '依依不捨', +'依托' => '依託', +'侵占' => '侵佔', +'侵并' => '侵併', +'侵占到' => '侵占到', +'侵占罪' => '侵占罪', +'便于' => '便於', +'便药' => '便藥', +'系数' => '係數', +'系为' => '係為', +'俄占' => '俄佔', +'保险柜' => '保險柜', +'信托贸易' => '信托貿易', +'信托' => '信託', +'修改后' => '修改後', +'修炼' => '修鍊', +'修胡刀' => '修鬍刀', +'俯冲' => '俯衝', +'个里' => '個裡', +'们斗了胆' => '們斗了膽', +'倒绷孩儿' => '倒繃孩兒', +'幸免' => '倖免', +'幸存' => '倖存', +'幸幸' => '倖幸', +'倛丑' => '倛醜', +'借听于聋' => '借聽於聾', +'倦游' => '倦遊', +'假药' => '假藥', +'假托' => '假託', +'假发' => '假髮', +'偎干' => '偎乾', +'偏后' => '偏後', +'偏于' => '偏於', +'做庄' => '做莊', +'停停当当' => '停停當當', +'停征' => '停徵', +'停制' => '停製', +'偷鸡不着' => '偷雞不著', +'伪药' => '偽藥', +'备注' => '備註', +'家伙' => '傢伙', +'家俱' => '傢俱', +'家具' => '傢具', +'催并' => '催併', +'佣中佼佼' => '傭中佼佼', +'佣人' => '傭人', +'佣仆' => '傭僕', +'佣兵' => '傭兵', +'佣工' => '傭工', +'佣懒' => '傭懶', +'佣书' => '傭書', +'佣金' => '傭金', +'傲霜斗雪' => '傲霜鬥雪', +'传位于四太子' => '傳位于四太子', +'传于' => '傳於', +'伤痕累累' => '傷痕纍纍', +'傻里傻气' => '傻裡傻氣', +'倾向于' => '傾向於', +'倾复' => '傾複', +'仆人' => '僕人', +'仆使' => '僕使', +'仆仆' => '僕僕', +'仆僮' => '僕僮', +'仆吏' => '僕吏', +'仆固怀恩' => '僕固懷恩', +'仆夫' => '僕夫', +'仆姑' => '僕姑', +'仆妇' => '僕婦', +'仆射' => '僕射', +'仆少' => '僕少', +'仆役' => '僕役', +'仆从' => '僕從', +'仆憎' => '僕憎', +'仆欧' => '僕歐', +'仆程' => '僕程', +'仆虽罢驽' => '僕雖罷駑', +'侥幸' => '僥倖', +'僮仆' => '僮僕', +'雇主' => '僱主', +'雇人' => '僱人', +'雇到' => '僱到', +'雇员' => '僱員', +'雇工' => '僱工', +'雇用' => '僱用', +'雇农' => '僱農', +'仪范' => '儀範', +'仪表' => '儀錶', +'亿多只' => '億多隻', +'亿天后' => '億天後', +'亿只' => '億隻', +'俭仆' => '儉僕', +'俭朴' => '儉樸', +'俭确之教' => '儉确之教', +'儒略改革历' => '儒略改革曆', +'儒略改革历史' => '儒略改革歷史', +'儒略历' => '儒略曆', +'儒略历史' => '儒略歷史', +'尽尽' => '儘儘', +'尽先' => '儘先', +'尽其所有' => '儘其所有', +'尽力' => '儘力', +'尽可能' => '儘可能', +'尽快' => '儘快', +'尽早' => '儘早', +'尽是' => '儘是', +'尽管' => '儘管', +'尽速' => '儘速', +'优于' => '優於', +'优游' => '優遊', +'兀术' => '兀朮', +'元凶' => '元兇', +'充饥' => '充饑', +'凶刀' => '兇刀', +'凶器' => '兇器', +'凶嫌' => '兇嫌', +'凶巴巴' => '兇巴巴', +'凶徒' => '兇徒', +'凶悍' => '兇悍', +'凶恶' => '兇惡', +'凶手' => '兇手', +'凶案' => '兇案', +'凶枪' => '兇槍', +'凶横' => '兇橫', +'凶殘' => '兇殘', +'凶残' => '兇殘', +'凶殺' => '兇殺', +'凶杀' => '兇殺', +'凶犯' => '兇犯', +'凶狠' => '兇狠', +'凶猛' => '兇猛', +'凶疑' => '兇疑', +'凶相' => '兇相', +'凶险' => '兇險', +'先占' => '先佔', +'先后' => '先後', +'先忧后乐' => '先憂後樂', +'先采' => '先採', +'先攻后守' => '先攻後守', +'先于' => '先於', +'先盛后衰' => '先盛後衰', +'先礼后兵' => '先禮後兵', +'先义后利' => '先義後利', +'先声后实' => '先聲後實', +'先苦后甘' => '先苦後甘', +'先赢后输' => '先贏後輸', +'先进后出' => '先進後出', +'先开花后结果' => '先開花後結果', +'光前裕后' => '光前裕後', +'光采' => '光採', +'光致致' => '光緻緻', +'克药' => '克藥', +'克复' => '克複', +'免于' => '免於', +'党参' => '党參', +'党太尉' => '党太尉', +'党怀英' => '党懷英', +'党进' => '党進', +'党项' => '党項', +'入夜后' => '入夜後', +'入伙' => '入夥', +'内心里' => '內心裡', +'内制' => '內製', +'内面包' => '內面包', +'内面包的' => '內面包的', +'内斗' => '內鬥', +'内哄' => '內鬨', +'全干' => '全乾', +'两天后' => '兩天後', +'两天晒网' => '兩天晒網', +'两扎' => '兩紮', +'两虎共斗' => '兩虎共鬥', +'两只' => '兩隻', +'两鼠斗穴' => '兩鼠鬥穴', +'八大胡同' => '八大胡同', +'八天后' => '八天後', +'八字胡' => '八字鬍', +'八扎' => '八紮', +'八蜡' => '八蜡', +'八只' => '八隻', +'公仔面' => '公仔麵', +'公仆' => '公僕', +'公元后' => '公元後', +'公孙丑' => '公孫丑', +'公干' => '公幹', +'公历' => '公曆', +'公历史' => '公歷史', +'公厘' => '公釐', +'公余' => '公餘', +'六划' => '六劃', +'六天后' => '六天後', +'六谷' => '六穀', +'六扎' => '六紮', +'六冲' => '六衝', +'六只' => '六隻', +'六出' => '六齣', +'其一只' => '其一只', +'其二只' => '其二只', +'其八九只' => '其八九只', +'其后' => '其後', +'其次辟地' => '其次辟地', +'其余' => '其餘', +'典范' => '典範', +'兼并' => '兼并', +'冉有仆' => '冉有僕', +'再于' => '再於', +'冗余' => '冗餘', +'冤仇' => '冤讎', +'冥蒙' => '冥濛', +'冬山庄' => '冬山庄', +'冬游' => '冬遊', +'冶游' => '冶遊', +'冶炼' => '冶鍊', +'冷庄子' => '冷莊子', +'冷面相' => '冷面相', +'冷面' => '冷麵', +'准不准他' => '准不准他', +'准不准你' => '准不准你', +'准不准她' => '准不准她', +'准不准它' => '准不准它', +'准不准我' => '准不准我', +'准不准许' => '准不准許', +'准不准谁' => '准不准誰', +'准保护' => '准保護', +'准保释' => '准保釋', +'凌蒙初' => '凌濛初', +'凝炼' => '凝鍊', +'几上' => '几上', +'几几' => '几几', +'几凳' => '几凳', +'几子' => '几子', +'几旁' => '几旁', +'几杖' => '几杖', +'几案' => '几案', +'几椅' => '几椅', +'几榻' => '几榻', +'几净窗明' => '几淨窗明', +'几筵' => '几筵', +'几丝' => '几絲', +'几面上' => '几面上', +'凡于' => '凡於', +'凶杀案' => '凶殺案', +'凶相毕露' => '凶相畢露', +'凹洞里' => '凹洞裡', +'出乖弄丑' => '出乖弄醜', +'出乖露丑' => '出乖露醜', +'出征收' => '出征收', +'出于' => '出於', +'出谋划策' => '出謀劃策', +'出游' => '出遊', +'出丑' => '出醜', +'出锤' => '出鎚', +'分布' => '分佈', +'分布于' => '分佈於', +'分占' => '分佔', +'分布区' => '分布區', +'分布图' => '分布圖', +'分布圖' => '分布圖', +'分散于' => '分散於', +'分钟' => '分鐘', +'刑余' => '刑餘', +'划一桨' => '划一槳', +'划了一会' => '划了一會', +'划来划去' => '划來划去', +'划到岸' => '划到岸', +'划到江心' => '划到江心', +'划得来' => '划得來', +'划着' => '划著', +'划着走' => '划著走', +'划龙舟' => '划龍舟', +'别后' => '別後', +'别于' => '別於', +'别日南鸿才北去' => '別日南鴻纔北去', +'别致' => '別緻', +'别庄' => '別莊', +'别着' => '別著', +'别辟' => '別闢', +'利欲' => '利慾', +'利于' => '利於', +'利欲熏心' => '利欲熏心', +'删后留位' => '刪後留位', +'删后缩位' => '刪後縮位', +'刮来刮去' => '刮來刮去', +'刮着' => '刮著', +'刮起来' => '刮起來', +'刮风下雪倒便宜' => '刮風下雪倒便宜', +'刮胡刀' => '刮鬍刀', +'制冷机' => '制冷機', +'制签' => '制籤', +'刺绣' => '刺繡', +'刻划' => '刻劃', +'刻于' => '刻於', +'刻钟' => '刻鐘', +'剃发' => '剃髮', +'剃须' => '剃鬚', +'削发' => '削髮', +'削面' => '削麵', +'克扣' => '剋扣', +'克日' => '剋日', +'克星' => '剋星', +'克期' => '剋期', +'克死' => '剋死', +'克薄' => '剋薄', +'前仰后合' => '前仰後合', +'前倨后恭' => '前倨後恭', +'前前后后' => '前前後後', +'前呼后拥' => '前呼後擁', +'前后' => '前後', +'前思后想' => '前思後想', +'前挽后推' => '前挽後推', +'前短后长' => '前短後長', +'前言不对后语' => '前言不對後語', +'前言不答后语' => '前言不答後語', +'前面店' => '前面店', +'剔庄货' => '剔莊貨', +'刚干' => '剛乾', +'刚雇' => '剛僱', +'刚才一载' => '剛纔一載', +'剥制' => '剝製', +'剩余' => '剩餘', +'剪牡丹喂牛' => '剪牡丹喂牛', +'剪彩' => '剪綵', +'剪发' => '剪髮', +'割舍' => '割捨', +'创获' => '創穫', +'创制' => '創製', +'铲出' => '剷出', +'铲刈' => '剷刈', +'铲平' => '剷平', +'铲除' => '剷除', +'铲头' => '剷頭', +'划一' => '劃一', +'划上' => '劃上', +'划下' => '劃下', +'划了' => '劃了', +'划出' => '劃出', +'划分' => '劃分', +'划到' => '劃到', +'划划' => '劃劃', +'划去' => '劃去', +'划在' => '劃在', +'划地' => '劃地', +'划定' => '劃定', +'划得' => '劃得', +'划成' => '劃成', +'划掉' => '劃掉', +'划拨' => '劃撥', +'划时代' => '劃時代', +'划款' => '劃款', +'划归' => '劃歸', +'划法' => '劃法', +'划清' => '劃清', +'划界' => '劃界', +'划破' => '劃破', +'划线' => '劃線', +'划足' => '劃足', +'划过' => '劃過', +'划开' => '劃開', +'剧药' => '劇藥', +'刘克庄' => '劉克莊', +'力拼' => '力拚', +'力拼众敌' => '力拼眾敵', +'力争上游' => '力爭上遊', +'功致' => '功緻', +'加于' => '加於', +'加氢精制' => '加氫精制', +'加药' => '加藥', +'加注' => '加註', +'劣于' => '劣於', +'助于' => '助於', +'劫后余生' => '劫後餘生', +'劫余' => '劫餘', +'勃郁' => '勃鬱', +'勇于' => '勇於', +'动荡' => '動蕩', +'胜于' => '勝於', +'劳力士表' => '勞力士錶', +'勤仆' => '勤僕', +'勤朴' => '勤樸', +'勋章' => '勳章', +'勺药' => '勺藥', +'勾干' => '勾幹', +'勾心斗角' => '勾心鬥角', +'勾魂荡魄' => '勾魂蕩魄', +'包准' => '包準', +'包谷' => '包穀', +'包扎' => '包紮', +'包庄' => '包莊', +'匏系' => '匏繫', +'北岳' => '北嶽', +'北回线' => '北迴線', +'北回铁路' => '北迴鐵路', +'匡复' => '匡複', +'匪干' => '匪幹', +'匿于' => '匿於', +'区划' => '區劃', +'十划' => '十劃', +'十多只' => '十多隻', +'十天后' => '十天後', +'十扎' => '十紮', +'十只' => '十隻', +'十出' => '十齣', +'千只可' => '千只可', +'千只够' => '千只夠', +'千只怕' => '千只怕', +'千只能' => '千只能', +'千只足够' => '千只足夠', +'千多只' => '千多隻', +'千天后' => '千天後', +'千扎' => '千紮', +'千丝万缕' => '千絲萬縷', +'千回百折' => '千迴百折', +'千回百转' => '千迴百轉', +'千钧一发' => '千鈞一髮', +'千只' => '千隻', +'升官发财' => '升官發財', +'午后' => '午後', +'半制品' => '半制品', +'半只可' => '半只可', +'半只够' => '半只夠', +'半于' => '半於', +'半只' => '半隻', +'南宫适' => '南宮适', +'南岳' => '南嶽', +'南筑' => '南筑', +'南回线' => '南迴線', +'南回铁路' => '南迴鐵路', +'南游' => '南遊', +'博汇' => '博彙', +'博采' => '博採', +'卞庄' => '卞莊', +'卞庄子' => '卞莊子', +'占了卜' => '占了卜', +'占便宜的是呆' => '占便宜的是獃', +'占卜' => '占卜', +'占多数' => '占多數', +'占有五不验' => '占有五不驗', +'占有权' => '占有權', +'印累绶若' => '印纍綬若', +'印制' => '印製', +'危于' => '危於', +'卵与石斗' => '卵與石鬥', +'卷须' => '卷鬚', +'厂部' => '厂部', +'厝薪于火' => '厝薪於火', +'原子钟' => '原子鐘', +'原于' => '原於', +'历物之意' => '厤物之意', +'厥后' => '厥後', +'参与' => '參与', +'参与者' => '參与者', +'参合' => '參合', +'参考价值' => '參考價值', +'参与人员' => '參與人員', +'参与制' => '參與制', +'参与感' => '參與感', +'参观团' => '參觀團', +'参观团体' => '參觀團體', +'参阅' => '參閱', +'及于' => '及於', +'反于' => '反於', +'反朴' => '反樸', +'反冲' => '反衝', +'反复制' => '反複製', +'反复' => '反覆', +'反覆' => '反覆', +'取信于' => '取信於', +'取舍' => '取捨', +'取材于' => '取材於', +'取决于' => '取決於', +'取法于' => '取法於', +'受制于' => '受制於', +'受托' => '受託', +'口干' => '口乾', +'口干冒' => '口干冒', +'口干政' => '口干政', +'口干涉' => '口干涉', +'口干犯' => '口干犯', +'口干预' => '口干預', +'口燥唇干' => '口燥唇乾', +'口腹之欲' => '口腹之慾', +'口里' => '口裡', +'古书云' => '古書云', +'古柯咸' => '古柯鹹', +'古朴' => '古樸', +'古语云' => '古語云', +'古迹' => '古迹', +'另于' => '另於', +'另辟' => '另闢', +'叩钟' => '叩鐘', +'只占' => '只佔', +'只占卜' => '只占卜', +'只占吉' => '只占吉', +'只占神问卜' => '只占神問卜', +'只占算' => '只占算', +'只采' => '只採', +'只冲' => '只衝', +'只身上已' => '只身上已', +'只身上有' => '只身上有', +'只身上没' => '只身上沒', +'只身上无' => '只身上無', +'只身上的' => '只身上的', +'只身世' => '只身世', +'只身份' => '只身份', +'只身前' => '只身前', +'只身受' => '只身受', +'只身子' => '只身子', +'只身形' => '只身形', +'只身影' => '只身影', +'只身后' => '只身後', +'只身心' => '只身心', +'只身旁' => '只身旁', +'只身材' => '只身材', +'只身段' => '只身段', +'只身为' => '只身為', +'只身边' => '只身邊', +'只身首' => '只身首', +'只身体' => '只身體', +'只身高' => '只身高', +'只采声' => '只采聲', +'叮叮当当' => '叮叮噹噹', +'叮当' => '叮噹', +'可于' => '可於', +'可紧可松' => '可緊可鬆', +'可自制' => '可自制', +'台子女' => '台子女', +'台子孙' => '台子孫', +'台布景' => '台布景', +'台后' => '台後', +'台历' => '台曆', +'台历史' => '台歷史', +'台面前' => '台面前', +'右后' => '右後', +'叶 恭弘' => '叶 恭弘', +'叶 恭弘' => '叶 恭弘', +'叶恭弘' => '叶恭弘', +'叶音' => '叶音', +'叶韵' => '叶韻', +'吃板刀面' => '吃板刀麵', +'吃着不尽' => '吃著不盡', +'吃姜' => '吃薑', +'吃药' => '吃藥', +'吃药后' => '吃藥後', +'吃里扒外' => '吃裡扒外', +'吃里爬外' => '吃裡爬外', +'吃辣面' => '吃辣麵', +'吃错药' => '吃錯藥', +'各辟' => '各闢', +'合伙人' => '合伙人', +'合并' => '合併', +'合伙' => '合夥', +'合府上' => '合府上', +'合采' => '合採', +'合于' => '合於', +'合历' => '合曆', +'合历史' => '合歷史', +'合准' => '合準', +'合着' => '合著', +'合著者' => '合著者', +'吉凶庆吊' => '吉凶慶弔', +'吊带裤' => '吊帶褲', +'吊挂着' => '吊掛著', +'吊杆' => '吊杆', +'吊着' => '吊著', +'吊裤' => '吊褲', +'吊裤带' => '吊褲帶', +'吊钟' => '吊鐘', +'同伙' => '同夥', +'同于' => '同於', +'后发座' => '后髮座', +'吐哺捉发' => '吐哺捉髮', +'吐哺握发' => '吐哺握髮', +'向往来' => '向往來', +'向往常' => '向往常', +'向往日' => '向往日', +'向往时' => '向往時', +'向后' => '向後', +'向着' => '向著', +'吝于' => '吝於', +'吞并' => '吞併', +'吟游' => '吟遊', +'含齿戴发' => '含齒戴髮', +'吹干' => '吹乾', +'吹发' => '吹髮', +'呆呆傻傻' => '呆呆傻傻', +'呆呆挣挣' => '呆呆掙掙', +'呆呆笨笨' => '呆呆笨笨', +'呆致致' => '呆緻緻', +'呆里呆气' => '呆裡呆氣', +'周历' => '周曆', +'周杰伦' => '周杰倫', +'周杰倫' => '周杰倫', +'周历史' => '周歷史', +'周庄王' => '周莊王', +'周游' => '周遊', +'呼吁' => '呼籲', +'命中注定' => '命中注定', +'和奸' => '和姦', +'咎征' => '咎徵', +'咬姜呷醋' => '咬薑呷醋', +'咯当' => '咯噹', +'咳嗽药' => '咳嗽藥', +'哀吊' => '哀弔', +'哀挽' => '哀輓', +'品汇' => '品彙', +'哄堂大笑' => '哄堂大笑', +'员山庄' => '員山庄', +'哪里' => '哪裡', +'哭脏' => '哭髒', +'唁吊' => '唁弔', +'呗赞' => '唄讚', +'唇干' => '唇乾', +'售后' => '售後', +'唯一只' => '唯一只', +'唱游' => '唱遊', +'唾面自干' => '唾面自乾', +'唾余' => '唾餘', +'商历' => '商曆', +'商历史' => '商歷史', +'啷当' => '啷噹', +'喂了一声' => '喂了一聲', +'善后' => '善後', +'善于' => '善於', +'喜向往' => '喜向往', +'喝干' => '喝乾', +'喧哄' => '喧鬨', +'丧钟' => '喪鐘', +'乔岳' => '喬嶽', +'单干' => '單幹', +'单打独斗' => '單打獨鬥', +'单只' => '單隻', +'嗑药' => '嗑藥', +'嗣后' => '嗣後', +'嘉谷' => '嘉穀', +'嘉肴' => '嘉肴', +'嘴里' => '嘴裏', +'恶心' => '噁心', +'噙齿戴发' => '噙齒戴髮', +'喷洒' => '噴洒', +'当啷' => '噹啷', +'当当' => '噹噹', +'噜苏' => '嚕囌', +'向导' => '嚮導', +'向往' => '嚮往', +'向应' => '嚮應', +'向迩' => '嚮邇', +'严于' => '嚴於', +'严丝合缝' => '嚴絲合縫', +'嚼谷' => '嚼穀', +'囉囉苏苏' => '囉囉囌囌', +'囉苏' => '囉囌', +'嘱托' => '囑託', +'四出征收' => '四出徵收', +'四分历' => '四分曆', +'四分历史' => '四分歷史', +'四天后' => '四天後', +'四舍五入' => '四捨五入', +'四扎' => '四紮', +'四只' => '四隻', +'四面包' => '四面包', +'四出' => '四齣', +'回采' => '回採', +'回旋加速' => '回旋加速', +'回历' => '回曆', +'回历史' => '回歷史', +'回丝' => '回絲', +'回着' => '回著', +'回荡' => '回蕩', +'回游' => '回遊', +'回阳荡气' => '回陽蕩氣', +'因于' => '因於', +'困倦起来' => '困倦起來', +'困于' => '困於', +'困兽之斗' => '困獸之鬥', +'困兽犹斗' => '困獸猶鬥', +'困斗' => '困鬥', +'固征' => '固徵', +'固于' => '固於', +'囿于' => '囿於', +'圈占' => '圈佔', +'圈子里' => '圈子裡', +'圈梁' => '圈樑', +'圈里' => '圈裡', +'国之桢干' => '國之楨榦', +'国家旅游局' => '國家旅游局', +'国于' => '國於', +'国历' => '國曆', +'国历代' => '國歷代', +'国历史' => '國歷史', +'国仇' => '國讎', +'园里' => '園裡', +'园游会' => '園遊會', +'图里' => '圖裡', +'图鉴' => '圖鑑', +'土里' => '土裡', +'土制' => '土製', +'土霉素' => '土霉素', +'在制品' => '在制品', +'在后' => '在後', +'在于' => '在於', +'地占' => '地佔', +'地方志' => '地方志', +'地志' => '地誌', +'地丑德齐' => '地醜德齊', +'坏于' => '坏於', +'坐庄' => '坐莊', +'坐钟' => '坐鐘', +'坑里' => '坑裡', +'坤范' => '坤範', +'坦荡' => '坦蕩', +'坦荡荡' => '坦蕩蕩', +'坱郁' => '坱鬱', +'垂于' => '垂於', +'垂发' => '垂髮', +'型范' => '型範', +'埃及历' => '埃及曆', +'埃及历史' => '埃及歷史', +'埃荣冲' => '埃榮衝', +'城里' => '城裡', +'基干' => '基幹', +'基于' => '基於', +'基准' => '基準', +'坚致' => '堅緻', +'涂着' => '塗著', +'涂药' => '塗藥', +'塞耳盗钟' => '塞耳盜鐘', +'塞药' => '塞藥', +'墓志铭' => '墓志銘', +'墓志' => '墓誌', +'增辟' => '增闢', +'墨沈' => '墨沈', +'墨沈未干' => '墨瀋未乾', +'堕胎药' => '墮胎藥', +'垦复' => '墾複', +'垦辟' => '墾闢', +'垄断价格' => '壟斷價格', +'垄断资产' => '壟斷資產', +'垄断集团' => '壟斷集團', +'壮游' => '壯遊', +'壮面' => '壯麵', +'壹郁' => '壹鬱', +'壶里' => '壺裡', +'壸范' => '壼範', +'寿面' => '壽麵', +'夏天里' => '夏天裡', +'夏历' => '夏曆', +'夏历史' => '夏歷史', +'夏游' => '夏遊', +'外强中干' => '外強中乾', +'外制' => '外製', +'多占' => '多佔', +'多划' => '多劃', +'多半只' => '多半只', +'多只是' => '多只是', +'多只需' => '多只需', +'多天后' => '多天後', +'多于' => '多於', +'多冲' => '多衝', +'多丑' => '多醜', +'多只' => '多隻', +'多余' => '多餘', +'多么' => '多麼', +'夜光表' => '夜光錶', +'夜里' => '夜裡', +'夜游' => '夜遊', +'梦有五不占' => '夢有五不占', +'梦里' => '夢裡', +'梦游' => '夢遊', +'伙伴' => '夥伴', +'伙友' => '夥友', +'伙同' => '夥同', +'伙众' => '夥眾', +'伙计' => '夥計', +'大丑' => '大丑', +'大伙儿' => '大伙兒', +'大伙' => '大夥', +'大干' => '大幹', +'大批涌到' => '大批湧到', +'大折儿' => '大摺兒', +'大于' => '大於', +'大明历' => '大明曆', +'大明历史' => '大明歷史', +'大历' => '大曆', +'大梁' => '大樑', +'大历史' => '大歷史', +'大呆' => '大獃', +'大目干连' => '大目乾連', +'大胆' => '大胆', +'大蜡' => '大蜡', +'大衍历' => '大衍曆', +'大衍历史' => '大衍歷史', +'大言非夸' => '大言非夸', +'大赞' => '大讚', +'大周折' => '大週摺', +'大金发苔' => '大金髮苔', +'大锤' => '大鎚', +'大只' => '大隻', +'大曲' => '大麴', +'天干物燥' => '天乾物燥', +'天克地冲' => '天克地衝', +'天后宫' => '天后宮', +'天后庙道' => '天后廟道', +'天干地支' => '天干地支', +'天后' => '天後', +'天文钟' => '天文鐘', +'天翻地覆' => '天翻地覆', +'天覆地载' => '天覆地載', +'太仆' => '太僕', +'太初历' => '太初曆', +'太初历史' => '太初歷史', +'夯干' => '夯幹', +'失于' => '失於', +'夸人' => '夸人', +'夸克' => '夸克', +'夸夸其谈' => '夸夸其談', +'夸姣' => '夸姣', +'夸容' => '夸容', +'夸毗' => '夸毗', +'夸父' => '夸父', +'夸特' => '夸特', +'夸脱' => '夸脫', +'夸诞' => '夸誕', +'夸诞不经' => '夸誕不經', +'夸丽' => '夸麗', +'奇迹' => '奇迹', +'奇丑' => '奇醜', +'奏折' => '奏摺', +'奏于' => '奏於', +'奥占' => '奧佔', +'夺斗' => '奪鬥', +'奋斗' => '奮鬥', +'女丑' => '女丑', +'女佣人' => '女佣人', +'女佣' => '女傭', +'女仆' => '女僕', +'奴仆' => '奴僕', +'奸淫掳掠' => '奸淫擄掠', +'好干' => '好乾', +'好家伙' => '好傢夥', +'好勇斗狠' => '好勇鬥狠', +'好斗大' => '好斗大', +'好斗室' => '好斗室', +'好斗笠' => '好斗笠', +'好斗篷' => '好斗篷', +'好斗胆' => '好斗膽', +'好斗蓬' => '好斗蓬', +'好于' => '好於', +'好呆' => '好獃', +'好困' => '好睏', +'好签' => '好籤', +'好丑' => '好醜', +'好斗' => '好鬥', +'如于' => '如於', +'如果干' => '如果幹', +'如饥似渴' => '如饑似渴', +'妙药' => '妙藥', +'始于' => '始於', +'委托' => '委託', +'委托书' => '委託書', +'奸夫' => '姦夫', +'奸妇' => '姦婦', +'奸宄' => '姦宄', +'奸情' => '姦情', +'奸杀' => '姦殺', +'奸污' => '姦汙', +'奸淫' => '姦淫', +'奸猾' => '姦猾', +'奸细' => '姦細', +'奸邪' => '姦邪', +'威棱' => '威稜', +'婚后' => '婚後', +'婢仆' => '婢僕', +'娲杆' => '媧杆', +'嫁于' => '嫁於', +'嫁祸于' => '嫁禍於', +'嫌凶' => '嫌兇', +'嫌好道丑' => '嫌好道醜', +'娴于' => '嫻於', +'嬉游' => '嬉遊', +'嬖幸' => '嬖倖', +'嬴余' => '嬴餘', +'子之丰兮' => '子之丰兮', +'子云' => '子云', +'字汇' => '字彙', +'字里行间' => '字裡行間', +'存十一于千百' => '存十一於千百', +'存折' => '存摺', +'存于' => '存於', +'季后赛' => '季後賽', +'孤寡不谷' => '孤寡不穀', +'宇宙志' => '宇宙誌', +'守先待后' => '守先待後', +'安于' => '安於', +'安沈铁路' => '安瀋鐵路', +'安眠药' => '安眠藥', +'安胎药' => '安胎藥', +'完工后' => '完工後', +'完成后' => '完成後', +'宗周钟' => '宗周鐘', +'官不怕大只怕管' => '官不怕大只怕管', +'官地为采' => '官地為寀', +'官历' => '官曆', +'官历史' => '官歷史', +'官庄' => '官莊', +'定于' => '定於', +'定准' => '定準', +'定制' => '定製', +'宜于' => '宜於', +'宣泄' => '宣洩', +'宦游' => '宦遊', +'宫里' => '宮裡', +'宰相肚里好撑船' => '宰相肚裡好撐船', +'宰相肚里能撑船' => '宰相肚裡能撐船', +'害于' => '害於', +'宴游' => '宴遊', +'家仆' => '家僕', +'家具备' => '家具備', +'家具有' => '家具有', +'家具木工科' => '家具木工科', +'家具行' => '家具行', +'家具体' => '家具體', +'家庄' => '家莊', +'家里' => '家裡', +'家丑' => '家醜', +'容后说明' => '容後說明', +'容于' => '容於', +'容范' => '容範', +'寄托在' => '寄托在', +'寄于' => '寄於', +'寄托' => '寄託', +'密致' => '密緻', +'寇准' => '寇準', +'寇仇' => '寇讎', +'富于' => '富於', +'富余' => '富餘', +'寒栗' => '寒慄', +'寒于' => '寒於', +'寓情于景' => '寓情於景', +'寓于' => '寓於', +'寓禁于征' => '寓禁於徵', +'寡占' => '寡佔', +'寡欲' => '寡慾', +'实干' => '實幹', +'写字台' => '寫字檯', +'宽宽松松' => '寬寬鬆鬆', +'宽于' => '寬於', +'宽余' => '寬餘', +'宽松' => '寬鬆', +'寮采' => '寮寀', +'宝山庄' => '寶山庄', +'宝历' => '寶曆', +'寶曆' => '寶曆', +'宝历史' => '寶歷史', +'宝庄' => '寶莊', +'宝里宝气' => '寶裡寶氣', +'封面里' => '封面裡', +'射雕' => '射鵰', +'将占' => '將佔', +'将占卜' => '將占卜', +'将于' => '將於', +'专向往' => '專向往', +'专注' => '專註', +'对折' => '對摺', +'对于' => '對於', +'对准' => '對準', +'对华发动' => '對華發動', +'对表中' => '對表中', +'对表扬' => '對表揚', +'对表明' => '對表明', +'对表演' => '對表演', +'对表现' => '對表現', +'对表达' => '對表達', +'对表' => '對錶', +'导游' => '導遊', +'小丑' => '小丑', +'小价' => '小价', +'小仆' => '小僕', +'小几' => '小几', +'小伙子' => '小夥子', +'小于' => '小於', +'小米面' => '小米麵', +'小只' => '小隻', +'少占' => '少佔', +'少采' => '少採', +'少于' => '少於', +'就于' => '就於', +'就范' => '就範', +'就里' => '就裡', +'就读于' => '就讀於', +'尸位素餐' => '尸位素餐', +'尸利' => '尸利', +'尸居余气' => '尸居餘氣', +'尸祝' => '尸祝', +'尸禄' => '尸祿', +'尸臣' => '尸臣', +'尸谏' => '尸諫', +'尸魂界' => '尸魂界', +'尸鸠' => '尸鳩', +'尼克松' => '尼克鬆', +'局里' => '局裡', +'屁股大吊了心' => '屁股大弔了心', +'居于' => '居於', +'屋子里' => '屋子裡', +'屋梁' => '屋樑', +'屋里' => '屋裡', +'屑于' => '屑於', +'屡顾尔仆' => '屢顧爾僕', +'属意于' => '屬意於', +'属于' => '屬於', +'属托' => '屬託', +'屯扎' => '屯紮', +'屯里' => '屯裡', +'山崩钟应' => '山崩鐘應', +'山岳' => '山嶽', +'山后' => '山後', +'山梁' => '山樑', +'山棱' => '山稜', +'山庄' => '山莊', +'山药' => '山藥', +'山里' => '山裡', +'山重水复' => '山重水複', +'岱岳' => '岱嶽', +'峰回' => '峰迴', +'峻岭' => '峻岭', +'昆剧' => '崑劇', +'昆山' => '崑山', +'昆仑' => '崑崙', +'昆仑山脉' => '崑崙山脈', +'昆曲' => '崑曲', +'昆腔' => '崑腔', +'昆苏' => '崑蘇', +'昆调' => '崑調', +'崖广' => '崖广', +'仑背' => '崙背', +'嶒棱' => '嶒稜', +'岳岳' => '嶽嶽', +'岳麓' => '嶽麓', +'川谷' => '川穀', +'巡回医疗' => '巡回醫療', +'巡回' => '巡迴', +'巡游' => '巡遊', +'工于' => '工於', +'工致' => '工緻', +'左后' => '左後', +'左冲右突' => '左衝右突', +'巧妇做不得无面馎饦' => '巧婦做不得無麵餺飥', +'巧干' => '巧幹', +'巧历' => '巧曆', +'巧历史' => '巧歷史', +'差之毫厘' => '差之毫厘', +'差之毫厘,谬以千里' => '差之毫釐,謬以千里', +'差于' => '差於', +'己丑' => '己丑', +'已占' => '已佔', +'已占卜' => '已占卜', +'已占算' => '已占算', +'已于' => '已於', +'巴尔干' => '巴爾幹', +'巷里' => '巷裡', +'市占' => '市佔', +'市占率' => '市佔率', +'市里' => '市裡', +'布谷' => '布穀', +'布庄' => '布莊', +'布谷鸟' => '布谷鳥', +'希伯来历' => '希伯來曆', +'希伯来历史' => '希伯來歷史', +'帘子' => '帘子', +'帘布' => '帘布', +'师范' => '師範', +'席卷' => '席捲', +'带团参加' => '帶團參加', +'带征' => '帶徵', +'带发修行' => '帶髮修行', +'幕后' => '幕後', +'帮佣' => '幫傭', +'干系' => '干係', +'干着急' => '干著急', +'平平当当' => '平平當當', +'平泉庄' => '平泉莊', +'平准' => '平準', +'年后' => '年後', +'年历' => '年曆', +'年历史' => '年歷史', +'年谷' => '年穀', +'年里' => '年裡', +'并力' => '并力', +'并吞' => '并吞', +'并州' => '并州', +'并日而食' => '并日而食', +'并行' => '并行', +'并迭' => '并迭', +'幸免于难' => '幸免於難', +'幸于' => '幸於', +'干上' => '幹上', +'干下去' => '幹下去', +'干不了' => '幹不了', +'干不成' => '幹不成', +'干了' => '幹了', +'干事' => '幹事', +'干些' => '幹些', +'干人' => '幹人', +'干什么' => '幹什麼', +'干个' => '幹個', +'干劲' => '幹勁', +'干劲冲天' => '幹勁沖天', +'干吏' => '幹吏', +'干员' => '幹員', +'干啥' => '幹啥', +'干吗' => '幹嗎', +'干嘛' => '幹嘛', +'干坏事' => '幹壞事', +'干完' => '幹完', +'干家' => '幹家', +'干将' => '幹將', +'干得' => '幹得', +'干性油' => '幹性油', +'干才' => '幹才', +'干掉' => '幹掉', +'干探' => '幹探', +'干校' => '幹校', +'干活' => '幹活', +'干流' => '幹流', +'干济' => '幹濟', +'干营生' => '幹營生', +'干父之蛊' => '幹父之蠱', +'干球温度' => '幹球溫度', +'干甚么' => '幹甚麼', +'干略' => '幹略', +'干当' => '幹當', +'干的停当' => '幹的停當', +'干细胞' => '幹細胞', +'干細胞' => '幹細胞', +'干线' => '幹線', +'干练' => '幹練', +'干缺' => '幹缺', +'干群关系' => '幹群關係', +'干蛊' => '幹蠱', +'干警' => '幹警', +'干起来' => '幹起來', +'干路' => '幹路', +'干办' => '幹辦', +'干这一行' => '幹這一行', +'干这种事' => '幹這種事', +'干道' => '幹道', +'干部' => '幹部', +'干革命' => '幹革命', +'干头' => '幹頭', +'干么' => '幹麼', +'几划' => '幾劃', +'几天后' => '幾天後', +'几于' => '幾於', +'几只' => '幾隻', +'几出' => '幾齣', +'广部' => '广部', +'庄稼人' => '庄稼人', +'庄稼院' => '庄稼院', +'店里' => '店裡', +'府干卿' => '府干卿', +'府干擾' => '府干擾', +'府干扰' => '府干擾', +'府干政' => '府干政', +'府干涉' => '府干涉', +'府干犯' => '府干犯', +'府干預' => '府干預', +'府干预' => '府干預', +'府干' => '府幹', +'府后' => '府後', +'座钟' => '座鐘', +'康庄大道' => '康庄大道', +'康采恩' => '康採恩', +'康庄' => '康莊', +'厨余' => '廚餘', +'厮斗' => '廝鬥', +'庙里' => '廟裡', +'广征' => '廣徵', +'广舍' => '廣捨', +'延后' => '延後', +'建于' => '建於', +'弄干' => '弄乾', +'弄丑' => '弄醜', +'弄脏' => '弄髒', +'弄松' => '弄鬆', +'弄鬼吊猴' => '弄鬼弔猴', +'吊儿郎当' => '弔兒郎當', +'吊卷' => '弔卷', +'吊取' => '弔取', +'吊古' => '弔古', +'吊古寻幽' => '弔古尋幽', +'吊唁' => '弔唁', +'吊问' => '弔問', +'吊喉' => '弔喉', +'吊丧' => '弔喪', +'吊丧问疾' => '弔喪問疾', +'吊喭' => '弔喭', +'吊场' => '弔場', +'吊奠' => '弔奠', +'吊孝' => '弔孝', +'吊客' => '弔客', +'吊宴' => '弔宴', +'吊带' => '弔帶', +'吊影' => '弔影', +'吊慰' => '弔慰', +'吊扣' => '弔扣', +'吊拷' => '弔拷', +'吊拷绷扒' => '弔拷繃扒', +'吊挂' => '弔掛', +'吊撒' => '弔撒', +'吊文' => '弔文', +'吊旗' => '弔旗', +'吊书' => '弔書', +'吊桥' => '弔橋', +'吊死' => '弔死', +'吊死问疾' => '弔死問疾', +'吊民' => '弔民', +'吊民伐罪' => '弔民伐罪', +'吊祭' => '弔祭', +'吊纸' => '弔紙', +'吊者大悦' => '弔者大悅', +'吊腰撒跨' => '弔腰撒跨', +'吊脚儿事' => '弔腳兒事', +'吊膀子' => '弔膀子', +'吊词' => '弔詞', +'吊诡' => '弔詭', +'吊诡矜奇' => '弔詭矜奇', +'吊谎' => '弔謊', +'吊贺迎送' => '弔賀迎送', +'吊头' => '弔頭', +'吊颈' => '弔頸', +'吊鹤' => '弔鶴', +'引斗' => '引鬥', +'弘历' => '弘曆', +'弘历史' => '弘歷史', +'弱于' => '弱於', +'弱水三千只取一瓢' => '弱水三千只取一瓢', +'张三丰' => '張三丰', +'張三丰' => '張三丰', +'张勋' => '張勳', +'强占' => '強佔', +'强加于' => '強加于', +'强加于人' => '強加於人', +'强奸' => '強姦', +'强干' => '強幹', +'强于' => '強於', +'别口气' => '彆口氣', +'别强' => '彆強', +'别扭' => '彆扭', +'别拗' => '彆拗', +'别气' => '彆氣', +'弹子台' => '彈子檯', +'弹珠台' => '彈珠檯', +'弹药' => '彈藥', +'汇刊' => '彙刊', +'汇报' => '彙報', +'汇整' => '彙整', +'汇算' => '彙算', +'汇编' => '彙編', +'汇纂' => '彙纂', +'汇辑' => '彙輯', +'汇集' => '彙集', +'形单影只' => '形單影隻', +'形影相吊' => '形影相弔', +'形于' => '形於', +'仿佛' => '彷彿', +'役于' => '役於', +'往后' => '往後', +'往日無仇' => '往日無讎', +'往肚里吞' => '往肚裡吞', +'往里' => '往裡', +'往复' => '往複', +'很干' => '很乾', +'很凶' => '很兇', +'很丑' => '很醜', +'律历志' => '律曆志', +'后上' => '後上', +'后下' => '後下', +'后世' => '後世', +'后主' => '後主', +'后事' => '後事', +'后人' => '後人', +'后代' => '後代', +'后仰' => '後仰', +'后件' => '後件', +'后任' => '後任', +'后作' => '後作', +'后来' => '後來', +'后偏' => '後偏', +'后备' => '後備', +'后传' => '後傳', +'后分' => '後分', +'后到' => '後到', +'后力不继' => '後力不繼', +'后劲' => '後勁', +'后勤' => '後勤', +'后区' => '後區', +'后半' => '後半', +'后印' => '後印', +'后厝路' => '後厝路', +'后去' => '後去', +'后台' => '後台', +'后台老板' => '後台老板', +'后向' => '後向', +'后周' => '後周', +'后唐' => '後唐', +'后嗣' => '後嗣', +'后园' => '後園', +'后图' => '後圖', +'后土' => '後土', +'后埔' => '後埔', +'后堂' => '後堂', +'后尘' => '後塵', +'后壁' => '後壁', +'后天' => '後天', +'后夫' => '後夫', +'后奏' => '後奏', +'后妻' => '後妻', +'后娘' => '後娘', +'后妇' => '後婦', +'后学' => '後學', +'后宫' => '後宮', +'后山' => '後山', +'后巷' => '後巷', +'后市' => '後市', +'后年' => '後年', +'后几' => '後幾', +'后庄' => '後庄', +'后序' => '後序', +'后座' => '後座', +'后庭' => '後庭', +'后悔' => '後悔', +'后患' => '後患', +'后房' => '後房', +'后手' => '後手', +'后排' => '後排', +'后掠角' => '後掠角', +'后接' => '後接', +'后援' => '後援', +'后撤' => '後撤', +'后攻' => '後攻', +'后放' => '後放', +'后效' => '後效', +'后文' => '後文', +'后方' => '後方', +'后于' => '後於', +'后日' => '後日', +'后昌路' => '後昌路', +'后晋' => '後晉', +'后晌' => '後晌', +'后晚' => '後晚', +'后景' => '後景', +'后会' => '後會', +'后有' => '後有', +'后望镜' => '後望鏡', +'后期' => '後期', +'后果' => '後果', +'后桅' => '後桅', +'后梁' => '後梁', +'后桥' => '後橋', +'后步' => '後步', +'后段' => '後段', +'后殿' => '後殿', +'后母' => '後母', +'后派' => '後派', +'后浪' => '後浪', +'后凉' => '後涼', +'后港' => '後港', +'后汉' => '後漢', +'后为' => '後為', +'后无来者' => '後無來者', +'后照镜' => '後照鏡', +'后燕' => '後燕', +'后父' => '後父', +'后现代' => '後現代', +'后生' => '後生', +'后用' => '後用', +'后由' => '後由', +'后盾' => '後盾', +'后知' => '後知', +'后知后觉' => '後知後覺', +'后福' => '後福', +'后秃' => '後禿', +'后秦' => '後秦', +'后空翻' => '後空翻', +'后窗' => '後窗', +'后站' => '後站', +'后端' => '後端', +'后竹围' => '後竹圍', +'后节' => '後節', +'后篇' => '後篇', +'后缀' => '後綴', +'后继' => '後繼', +'后续' => '後續', +'后置' => '後置', +'后者' => '後者', +'后肢' => '後肢', +'后背' => '後背', +'后脑' => '後腦', +'后脚' => '後腳', +'后腿' => '後腿', +'后膛' => '後膛', +'后花园' => '後花園', +'后菜园' => '後菜園', +'后叶' => '後葉', +'后行' => '後行', +'后街' => '後街', +'后卫' => '後衛', +'后裔' => '後裔', +'后补' => '後補', +'后䙓' => '後襬', +'后视镜' => '後視鏡', +'后言' => '後言', +'后计' => '後計', +'后记' => '後記', +'后设' => '後設', +'后读' => '後讀', +'后走' => '後走', +'后起' => '後起', +'后赵' => '後趙', +'后足' => '後足', +'后跟' => '後跟', +'后路' => '後路', +'后身' => '後身', +'后车' => '後車', +'后辈' => '後輩', +'后轮' => '後輪', +'后转' => '後轉', +'后述' => '後述', +'后退' => '後退', +'后送' => '後送', +'后进' => '後進', +'后过' => '後過', +'后遗症' => '後遺症', +'后边' => '後邊', +'后部' => '後部', +'后镜' => '後鏡', +'后门' => '後門', +'后防' => '後防', +'后院' => '後院', +'后集' => '後集', +'后面' => '後面', +'后面店' => '後面店', +'后项' => '後項', +'后头' => '後頭', +'后颈' => '後頸', +'后顾' => '後顧', +'后魏' => '後魏', +'后点' => '後點', +'后龙' => '後龍', +'徐干' => '徐幹', +'徒托空言' => '徒託空言', +'得于' => '得於', +'徜徉于' => '徜徉於', +'从事于' => '從事於', +'从于' => '從於', +'从里到外' => '從裡到外', +'从里向外' => '從裡向外', +'复始' => '復始', +'复仇' => '復讎', +'征人' => '徵人', +'征令' => '徵令', +'征占' => '徵佔', +'征信' => '徵信', +'征候' => '徵候', +'征兆' => '徵兆', +'征兵' => '徵兵', +'征到' => '徵到', +'征募' => '徵募', +'征友' => '徵友', +'征召' => '徵召', +'征名责实' => '徵名責實', +'征吏' => '徵吏', +'征咎' => '徵咎', +'征启' => '徵啟', +'征士' => '徵士', +'征婚' => '徵婚', +'征实' => '徵實', +'征庸' => '徵庸', +'征引' => '徵引', +'征得' => '徵得', +'征怪' => '徵怪', +'征才' => '徵才', +'征招' => '徵招', +'征收' => '徵收', +'征效' => '徵效', +'征文' => '徵文', +'征求' => '徵求', +'征状' => '徵狀', +'征用' => '徵用', +'征发' => '徵發', +'征税' => '徵稅', +'征稿' => '徵稿', +'征答' => '徵答', +'征结' => '徵結', +'征圣' => '徵聖', +'征聘' => '徵聘', +'征训' => '徵訓', +'征询' => '徵詢', +'征调' => '徵調', +'征象' => '徵象', +'征购' => '徵購', +'征迹' => '徵跡', +'征车' => '徵車', +'征辟' => '徵辟', +'征逐' => '徵逐', +'征选' => '徵選', +'征集' => '徵集', +'征风召雨' => '徵風召雨', +'征验' => '徵驗', +'德占' => '德佔', +'心愿' => '心愿', +'心于' => '心於', +'心细如发' => '心細如髮', +'心荡' => '心蕩', +'心药' => '心藥', +'心里' => '心裏', +'心里不安' => '心裡不安', +'心里有数' => '心裡有數', +'心里话' => '心裡話', +'心里头' => '心裡頭', +'心长发短' => '心長髮短', +'心余' => '心餘', +'志于' => '志於', +'忙并' => '忙併', +'忙于' => '忙於', +'忙里' => '忙裡', +'忙里偷闲' => '忙裡偷閒', +'忠人之托' => '忠人之托', +'忠仆' => '忠僕', +'忠于' => '忠於', +'快干' => '快乾', +'快快当当' => '快快當當', +'快冲' => '快衝', +'忽前忽后' => '忽前忽後', +'怎么' => '怎麼', +'怎么着' => '怎麼著', +'怒于' => '怒於', +'怒发冲冠' => '怒髮衝冠', +'思前思后' => '思前思後', +'思前想后' => '思前想後', +'思如泉涌' => '思如泉湧', +'怠于' => '怠於', +'急于' => '急於', +'急冲而下' => '急衝而下', +'性征' => '性徵', +'性欲' => '性慾', +'怪里怪气' => '怪裡怪氣', +'怫郁' => '怫鬱', +'怯于' => '怯於', +'恂栗' => '恂慄', +'恒生指数' => '恒生指數', +'恒生股价指数' => '恒生股價指數', +'恒生银行' => '恒生銀行', +'恕乏价催' => '恕乏价催', +'息交绝游' => '息交絕遊', +'息谷' => '息穀', +'恰才' => '恰纔', +'悍药' => '悍藥', +'悒郁' => '悒鬱', +'悠悠荡荡' => '悠悠蕩蕩', +'悠荡' => '悠蕩', +'悠游' => '悠遊', +'悲筑' => '悲筑', +'悲郁' => '悲鬱', +'闷在心里' => '悶在心裡', +'闷着头儿干' => '悶著頭兒幹', +'悸栗' => '悸慄', +'情欲' => '情慾', +'惇朴' => '惇樸', +'恶直丑正' => '惡直醜正', +'恶斗' => '惡鬥', +'惴栗' => '惴慄', +'意占' => '意佔', +'意大利面' => '意大利麵', +'意面' => '意麵', +'爱在心里' => '愛在心裡', +'爱困' => '愛睏', +'感冒药' => '感冒藥', +'感于' => '感於', +'愧于' => '愧於', +'愿朴' => '愿樸', +'愿而恭' => '愿而恭', +'栗冽' => '慄冽', +'栗栗' => '慄慄', +'慌里慌张' => '慌裡慌張', +'惯于' => '慣於', +'庆吊' => '慶弔', +'庆历' => '慶曆', +'庆历史' => '慶歷史', +'欲令智昏' => '慾令智昏', +'欲壑难填' => '慾壑難填', +'欲念' => '慾念', +'欲望' => '慾望', +'欲海' => '慾海', +'欲火' => '慾火', +'欲障' => '慾障', +'忧郁' => '憂鬱', +'凭几' => '憑几', +'凭吊' => '憑弔', +'凭折' => '憑摺', +'凭准' => '憑準', +'凭借' => '憑藉', +'凭借着' => '憑藉著', +'恳托' => '懇託', +'懈松' => '懈鬆', +'应征' => '應徵', +'应钟' => '應鐘', +'懔栗' => '懍慄', +'蒙懂' => '懞懂', +'蒙蒙懂懂' => '懞懞懂懂', +'蒙直' => '懞直', +'惩前毖后' => '懲前毖後', +'惩忿窒欲' => '懲忿窒欲', +'懒于' => '懶於', +'怀里' => '懷裡', +'怀表' => '懷錶', +'悬挂' => '懸挂', +'悬梁' => '懸樑', +'悬臂梁' => '懸臂樑', +'悬钟' => '懸鐘', +'惧于' => '懼於', +'懿范' => '懿範', +'恋恋不舍' => '戀戀不捨', +'成于' => '成於', +'成药' => '成藥', +'或于' => '或於', +'戬谷' => '戩穀', +'截发' => '截髮', +'战天斗地' => '戰天鬥地', +'战后' => '戰後', +'战栗' => '戰慄', +'战斗' => '戰鬥', +'戏彩娱亲' => '戲綵娛親', +'戴表' => '戴錶', +'戴发含齿' => '戴髮含齒', +'房里' => '房裡', +'所云' => '所云', +'所云云' => '所云云', +'所占' => '所佔', +'所占卜' => '所占卜', +'所占星' => '所占星', +'所占算' => '所占算', +'所托' => '所託', +'扁拟谷盗虫' => '扁擬穀盜蟲', +'手塚治虫' => '手塚治虫', +'手冢治虫' => '手塚治虫', +'手折' => '手摺', +'手表态' => '手表態', +'手表明' => '手表明', +'手表决' => '手表決', +'手表演' => '手表演', +'手表现' => '手表現', +'手表示' => '手表示', +'手表达' => '手表達', +'手表露' => '手表露', +'手表面' => '手表面', +'手里' => '手裡', +'手表' => '手錶', +'手松' => '手鬆', +'才干休' => '才干休', +'才干戈' => '才干戈', +'才干扰' => '才干擾', +'才干政' => '才干政', +'才干涉' => '才干涉', +'才干预' => '才干預', +'才干' => '才幹', +'扎好底子' => '扎好底子', +'扎好根' => '扎好根', +'扑作教刑' => '扑作教刑', +'扑打' => '扑打', +'扑挞' => '扑撻', +'打干哕' => '打乾噦', +'打并' => '打併', +'打出吊入' => '打出弔入', +'打卡钟' => '打卡鐘', +'打吨' => '打吨', +'打干' => '打幹', +'打拼' => '打拚', +'打谷' => '打穀', +'打路庄板' => '打路莊板', +'打钟' => '打鐘', +'打斗' => '打鬥', +'托福考' => '托福考', +'托管国' => '托管國', +'扞御' => '扞禦', +'扯面' => '扯麵', +'扶余国' => '扶餘國', +'批准的' => '批准的', +'批复' => '批複', +'批注' => '批註', +'批斗' => '批鬥', +'承先启后' => '承先啟後', +'承前启后' => '承前啟後', +'承制' => '承製', +'抑制作用' => '抑制作用', +'抑郁' => '抑鬱', +'抓奸' => '抓姦', +'抓药' => '抓藥', +'抓斗' => '抓鬥', +'投药' => '投藥', +'抗癌药' => '抗癌藥', +'抗御' => '抗禦', +'抗药' => '抗藥', +'折向往' => '折向往', +'折子戏' => '折子戲', +'折戟沈河' => '折戟沈河', +'折冲' => '折衝', +'披榛采兰' => '披榛採蘭', +'披头散发' => '披頭散髮', +'披发' => '披髮', +'抱朴而长吟兮' => '抱朴而長吟兮', +'抱素怀朴' => '抱素懷樸', +'抵御' => '抵禦', +'抹干' => '抹乾', +'抽公签' => '抽公籤', +'抽签' => '抽籤', +'抿发' => '抿髮', +'拆伙' => '拆夥', +'拈须' => '拈鬚', +'拉杆' => '拉杆', +'拉纤' => '拉縴', +'拉面上' => '拉面上', +'拉面具' => '拉面具', +'拉面前' => '拉面前', +'拉面巾' => '拉面巾', +'拉面无' => '拉面無', +'拉面皮' => '拉面皮', +'拉面罩' => '拉面罩', +'拉面色' => '拉面色', +'拉面部' => '拉面部', +'拉面' => '拉麵', +'拒人于' => '拒人於', +'拒于' => '拒於', +'拓朴' => '拓樸', +'拗别' => '拗彆', +'拘于' => '拘於', +'拙于' => '拙於', +'拙朴' => '拙樸', +'拼命' => '拚命', +'拼舍' => '拚捨', +'拼死' => '拚死', +'拼斗' => '拚鬥', +'拜托' => '拜託', +'括发' => '括髮', +'拭干' => '拭乾', +'拮据' => '拮据', +'拼死拼活' => '拼死拼活', +'拾沈' => '拾瀋', +'拿准' => '拿準', +'拿破仑' => '拿破崙', +'挂名' => '挂名', +'挂图' => '挂圖', +'挂帅' => '挂帥', +'挂彩' => '挂彩', +'挂念' => '挂念', +'挂号' => '挂號', +'挂车' => '挂車', +'挂钩' => '挂鉤', +'挂面' => '挂面', +'指手划脚' => '指手劃腳', +'挌斗' => '挌鬥', +'挑斗' => '挑鬥', +'振荡' => '振蕩', +'捆扎' => '捆紮', +'捉奸徒' => '捉奸徒', +'捉奸细' => '捉奸細', +'捉奸贼' => '捉奸賊', +'捉奸党' => '捉奸黨', +'捉奸' => '捉姦', +'捉发' => '捉髮', +'捍御' => '捍禦', +'捏面人' => '捏麵人', +'舍不得' => '捨不得', +'舍出' => '捨出', +'舍去' => '捨去', +'舍命' => '捨命', +'舍堕' => '捨墮', +'舍安就危' => '捨安就危', +'舍实' => '捨實', +'舍己从人' => '捨己從人', +'舍己救人' => '捨己救人', +'舍己为人' => '捨己為人', +'舍己为公' => '捨己為公', +'舍己为国' => '捨己為國', +'舍得' => '捨得', +'舍我其谁' => '捨我其誰', +'舍本逐末' => '捨本逐末', +'舍弃' => '捨棄', +'舍死忘生' => '捨死忘生', +'舍生' => '捨生', +'舍短取长' => '捨短取長', +'舍身' => '捨身', +'舍车保帅' => '捨車保帥', +'舍近求远' => '捨近求遠', +'卷住' => '捲住', +'卷来' => '捲來', +'卷儿' => '捲兒', +'卷入' => '捲入', +'卷动' => '捲動', +'卷去' => '捲去', +'卷图' => '捲圖', +'卷土重来' => '捲土重來', +'卷尺' => '捲尺', +'卷心菜' => '捲心菜', +'卷成' => '捲成', +'卷曲' => '捲曲', +'卷款' => '捲款', +'卷毛' => '捲毛', +'卷烟' => '捲煙', +'卷筒' => '捲筒', +'卷帘' => '捲簾', +'卷纸' => '捲紙', +'卷缩' => '捲縮', +'卷舌' => '捲舌', +'卷舖盖' => '捲舖蓋', +'卷菸' => '捲菸', +'卷袖' => '捲袖', +'卷走' => '捲走', +'卷起' => '捲起', +'卷轴' => '捲軸', +'卷逃' => '捲逃', +'卷铺盖' => '捲鋪蓋', +'卷云' => '捲雲', +'卷风' => '捲風', +'卷发' => '捲髮', +'捵面' => '捵麵', +'捶炼' => '捶鍊', +'扫荡' => '掃蕩', +'掌柜' => '掌柜', +'排骨面' => '排骨麵', +'挂帘' => '掛帘', +'挂钟' => '掛鐘', +'采下' => '採下', +'采伐' => '採伐', +'采住' => '採住', +'采信' => '採信', +'采光' => '採光', +'采到' => '採到', +'采制' => '採制', +'采区' => '採區', +'采去' => '採去', +'采取' => '採取', +'采回' => '採回', +'采在' => '採在', +'采好' => '採好', +'采得' => '採得', +'采拾' => '採拾', +'采挖' => '採挖', +'采掘' => '採掘', +'采摘' => '採摘', +'采摭' => '採摭', +'采择' => '採擇', +'采撷' => '採擷', +'采收' => '採收', +'采料' => '採料', +'采暖' => '採暖', +'采桑' => '採桑', +'采样' => '採樣', +'采樵人' => '採樵人', +'采树种' => '採樹種', +'采气' => '採氣', +'采油' => '採油', +'采为' => '採為', +'采煤' => '採煤', +'采获' => '採獲', +'采猎' => '採獵', +'采珠' => '採珠', +'采生折割' => '採生折割', +'采用' => '採用', +'采的' => '採的', +'采石' => '採石', +'采砂场' => '採砂場', +'采矿' => '採礦', +'采种' => '採種', +'采空区' => '採空區', +'采空采穗' => '採空採穗', +'采纳' => '採納', +'采给' => '採給', +'采花' => '採花', +'采芹人' => '採芹人', +'采茶' => '採茶', +'采菊' => '採菊', +'采莲' => '採蓮', +'采薇' => '採薇', +'采薪' => '採薪', +'采药' => '採藥', +'采行' => '採行', +'采补' => '採補', +'采访' => '採訪', +'采证' => '採證', +'采买' => '採買', +'采购' => '採購', +'采办' => '採辦', +'采运' => '採運', +'采过' => '採過', +'采选' => '採選', +'采金' => '採金', +'采录' => '採錄', +'采铁' => '採鐵', +'采集' => '採集', +'采风' => '採風', +'采风问俗' => '採風問俗', +'采食' => '採食', +'采盐' => '採鹽', +'掣签' => '掣籤', +'接着说' => '接著說', +'推情准理' => '推情準理', +'推托之词' => '推托之詞', +'推舟于陆' => '推舟於陸', +'推托' => '推託', +'提子干' => '提子乾', +'提心吊胆' => '提心弔膽', +'提摩太后书' => '提摩太後書', +'插于' => '插於', +'换签' => '換籤', +'换药' => '換藥', +'换只' => '換隻', +'换发' => '換髮', +'握发' => '握髮', +'揩干' => '揩乾', +'揪采' => '揪採', +'揭丑' => '揭醜', +'挥手表' => '揮手表', +'挥杆' => '揮杆', +'搋面' => '搋麵', +'损于' => '損於', +'搏斗' => '搏鬥', +'摇摇荡荡' => '搖搖蕩蕩', +'摇荡' => '搖蕩', +'捣鬼吊白' => '搗鬼弔白', +'搤肮拊背' => '搤肮拊背', +'搬斗' => '搬鬥', +'搭干铺' => '搭乾鋪', +'搭伙' => '搭夥', +'抢占' => '搶佔', +'搽药' => '搽藥', +'摧坚获丑' => '摧堅獲醜', +'摭采' => '摭採', +'摸棱' => '摸稜', +'折合' => '摺合', +'折奏' => '摺奏', +'折子' => '摺子', +'折尺' => '摺尺', +'折扇' => '摺扇', +'折梯' => '摺梯', +'折椅' => '摺椅', +'折叠' => '摺疊', +'折痕' => '摺痕', +'折篷' => '摺篷', +'折纸' => '摺紙', +'折裙' => '摺裙', +'撇吊' => '撇弔', +'捞干' => '撈乾', +'捞面' => '撈麵', +'撚须' => '撚鬚', +'撞木钟' => '撞木鐘', +'撞球台' => '撞球檯', +'撞钟' => '撞鐘', +'撞阵冲军' => '撞陣衝軍', +'撤并' => '撤併', +'撤后' => '撤後', +'拨谷' => '撥穀', +'撩斗' => '撩鬥', +'播于' => '播於', +'扑冬' => '撲鼕', +'扑冬冬' => '撲鼕鼕', +'擀面' => '擀麵', +'擅于' => '擅於', +'击扑' => '擊扑', +'击钟' => '擊鐘', +'担仔面' => '擔仔麵', +'担担面' => '擔擔麵', +'担着' => '擔著', +'担负着' => '擔負著', +'擘划' => '擘劃', +'据云' => '據云', +'据干而窥井底' => '據榦而窺井底', +'擢发' => '擢髮', +'擦干' => '擦乾', +'擦干净' => '擦乾淨', +'擦药' => '擦藥', +'拟于' => '擬於', +'拧干' => '擰乾', +'摆钟' => '擺鐘', +'摄于' => '攝於', +'摄制' => '攝製', +'支干' => '支幹', +'支杆' => '支杆', +'收获' => '收穫', +'改征' => '改徵', +'改于' => '改於', +'攻占' => '攻佔', +'放在心里' => '放在心裡', +'放蒙挣' => '放懞掙', +'放荡' => '放蕩', +'放松' => '放鬆', +'故于' => '故於', +'敏于' => '敏於', +'敏于事而慎于言' => '敏於事而慎於言', +'救药' => '救藥', +'败于' => '敗於', +'叙说着' => '敘說著', +'教于' => '教於', +'教范' => '教範', +'敢干' => '敢幹', +'敢情欲' => '敢情欲', +'敢斗了胆' => '敢斗了膽', +'敢于' => '敢於', +'散伙' => '散夥', +'散于' => '散於', +'散荡' => '散蕩', +'敦朴' => '敦樸', +'敬挽' => '敬輓', +'敲扑' => '敲扑', +'敲钟' => '敲鐘', +'整庄' => '整莊', +'整只' => '整隻', +'敌后' => '敵後', +'敌忾同仇' => '敵愾同讎', +'敷药' => '敷藥', +'数天后' => '數天後', +'数罪并罚' => '數罪併罰', +'数与虏确' => '數與虜确', +'文丑' => '文丑', +'文汇报' => '文匯報', +'文后' => '文後', +'文征明' => '文徵明', +'文思泉涌' => '文思泉湧', +'文采郁郁' => '文采郁郁', +'斗转参横' => '斗轉參橫', +'斫雕为朴' => '斫雕為樸', +'新历' => '新曆', +'新历史' => '新歷史', +'新扎' => '新紮', +'新庄' => '新莊', +'新庄市' => '新莊市', +'斲雕为朴' => '斲雕為樸', +'断后' => '斷後', +'断发' => '斷髮', +'断发文身' => '斷髮文身', +'方便面' => '方便麵', +'方几' => '方几', +'方向往' => '方向往', +'方志' => '方誌', +'方面' => '方面', +'于0' => '於0', +'于1' => '於1', +'于2' => '於2', +'于3' => '於3', +'于4' => '於4', +'于5' => '於5', +'于6' => '於6', +'于7' => '於7', +'于8' => '於8', +'于9' => '於9', +'于一' => '於一', +'于一役' => '於一役', +'于七' => '於七', +'于三' => '於三', +'于世' => '於世', +'于之' => '於之', +'于乎' => '於乎', +'于九' => '於九', +'于事' => '於事', +'于二' => '於二', +'于五' => '於五', +'于人' => '於人', +'于今' => '於今', +'于他' => '於他', +'于伏' => '於伏', +'于何' => '於何', +'于你' => '於你', +'于八' => '於八', +'于六' => '於六', +'于前' => '於前', +'于劣' => '於劣', +'于勤' => '於勤', +'于十' => '於十', +'于半' => '於半', +'于呼哀哉' => '於呼哀哉', +'于四' => '於四', +'于国' => '於國', +'于坏' => '於坏', +'于垂' => '於垂', +'于夫罗' => '於夫羅', +'於夫罗' => '於夫羅', +'於夫羅' => '於夫羅', +'于她' => '於她', +'于好' => '於好', +'于始' => '於始', +'於姓' => '於姓', +'于它' => '於它', +'于家' => '於家', +'于密' => '於密', +'于左' => '於左', +'于差' => '於差', +'于己' => '於己', +'于市' => '於市', +'于幕' => '於幕', +'于幼华' => '於幼華', +'于弱' => '於弱', +'于强' => '於強', +'于征' => '於征', +'于后' => '於後', +'于心' => '於心', +'于思' => '於思', +'于怀' => '於懷', +'于我' => '於我', +'于戏' => '於戲', +'于敝' => '於敝', +'于斯' => '於斯', +'于于' => '於於', +'于是' => '於是', +'于是乎' => '於是乎', +'于时' => '於時', +'于梨华' => '於梨華', +'於梨華' => '於梨華', +'于乐' => '於樂', +'于此' => '於此', +'於氏' => '於氏', +'于民' => '於民', +'于水' => '於水', +'于法' => '於法', +'于潜县' => '於潛縣', +'于火' => '於火', +'于焉' => '於焉', +'于墙' => '於牆', +'于物' => '於物', +'于田' => '於田', +'于毕' => '於畢', +'于尽' => '於盡', +'于盲' => '於盲', +'于祂' => '於祂', +'于穆' => '於穆', +'于终' => '於終', +'于美' => '於美', +'于色' => '於色', +'于菟' => '於菟', +'于蓝' => '於藍', +'于行' => '於行', +'于衷' => '於衷', +'于该' => '於該', +'于农' => '於農', +'于途' => '於途', +'于过' => '於過', +'于邑' => '於邑', +'于丑' => '於醜', +'于野' => '於野', +'于陆' => '於陸', +'于飞' => '於飛', +'于0' => '於0', +'于1' => '於1', +'于2' => '於2', +'于3' => '於3', +'于4' => '於4', +'于5' => '於5', +'于6' => '於6', +'于7' => '於7', +'于8' => '於8', +'于9' => '於9', +'施舍' => '施捨', +'施于' => '施於', +'施舍之道' => '施舍之道', +'施药' => '施藥', +'旁征博引' => '旁徵博引', +'旁注' => '旁註', +'旅游业' => '旅游業', +'旅游' => '旅遊', +'旋干转坤' => '旋乾轉坤', +'旋绕着' => '旋繞著', +'旋回' => '旋迴', +'族里' => '族裡', +'旗杆' => '旗杆', +'日占' => '日佔', +'日后' => '日後', +'日晒' => '日晒', +'日历' => '日曆', +'日历史' => '日歷史', +'日志' => '日誌', +'早于' => '早於', +'旱干' => '旱乾', +'昆仑山' => '昆崙山', +'升平' => '昇平', +'升阳' => '昇陽', +'昊天不吊' => '昊天不弔', +'明征' => '明徵', +'明于' => '明於', +'明目张胆' => '明目張胆', +'明窗净几' => '明窗淨几', +'明范' => '明範', +'明里' => '明裡', +'易于' => '易於', +'星历' => '星曆', +'星期后' => '星期後', +'星历史' => '星歷史', +'星辰表' => '星辰錶', +'春假里' => '春假裡', +'春天里' => '春天裡', +'春药' => '春藥', +'春游' => '春遊', +'春香斗学' => '春香鬥學', +'昧于' => '昧於', +'时钟' => '時鐘', +'晃荡' => '晃蕩', +'晋升' => '晉陞', +'晒干' => '晒乾', +'晒伤' => '晒傷', +'晒图' => '晒圖', +'晒图纸' => '晒圖紙', +'晒成' => '晒成', +'晒晒' => '晒晒', +'晒烟' => '晒煙', +'晒种' => '晒種', +'晒衣' => '晒衣', +'晒黑' => '晒黑', +'晞发' => '晞髮', +'晨钟' => '晨鐘', +'普冬冬' => '普鼕鼕', +'景致' => '景緻', +'晾干' => '晾乾', +'晕船药' => '暈船藥', +'晕车药' => '暈車藥', +'暗地里' => '暗地裡', +'暗沟里' => '暗溝裡', +'暗里' => '暗裡', +'暗斗' => '暗鬥', +'畅游' => '暢遊', +'暂于' => '暫於', +'暴敛横征' => '暴斂橫徵', +'暴晒' => '暴晒', +'历元' => '曆元', +'历命' => '曆命', +'历始' => '曆始', +'历室' => '曆室', +'历尾' => '曆尾', +'历数' => '曆數', +'历日' => '曆日', +'历书' => '曆書', +'历本' => '曆本', +'历法' => '曆法', +'历纪' => '曆紀', +'历象' => '曆象', +'曝晒' => '曝晒', +'晒谷' => '曬穀', +'曰云' => '曰云', +'更仆难数' => '更僕難數', +'更签' => '更籤', +'书后' => '書後', +'书呆子' => '書獃子', +'书签' => '書籤', +'曼谷人' => '曼谷人', +'曾于' => '曾於', +'曾朴' => '曾樸', +'最多只' => '最多只', +'最后' => '最後', +'最里面' => '最裡面', +'会占' => '會佔', +'会占卜' => '會占卜', +'会干' => '會幹', +'会吊' => '會弔', +'会后' => '會後', +'会于' => '會於', +'会里' => '會裡', +'月后' => '月後', +'月历' => '月曆', +'月历史' => '月歷史', +'月离于毕' => '月離於畢', +'月丽于箕' => '月麗於箕', +'有仆' => '有僕', +'有够赞' => '有夠讚', +'有征' => '有徵', +'有恒街' => '有恒街', +'有栖川' => '有栖川', +'有准' => '有準', +'有棱有角' => '有稜有角', +'有鉴于' => '有鑑於', +'有鉴于此' => '有鑒於此', +'有只' => '有隻', +'有余' => '有餘', +'有发头陀寺' => '有髮頭陀寺', +'服务于' => '服務於', +'服于' => '服於', +'服药' => '服藥', +'望了望' => '望了望', +'朝乾夕惕' => '朝乾夕惕', +'朝后' => '朝後', +'朝钟' => '朝鐘', +'朦胧' => '朦朧', +'蒙胧' => '朦朧', +'木偶戏扎' => '木偶戲紮', +'木杆' => '木杆', +'木材干馏' => '木材乾餾', +'木梁' => '木樑', +'木制' => '木製', +'未干' => '未乾', +'末药' => '末藥', +'本征' => '本徵', +'术赤' => '朮赤', +'朱仑街' => '朱崙街', +'朱庆余' => '朱慶餘', +'朱理安历' => '朱理安曆', +'朱理安历史' => '朱理安歷史', +'杆子' => '杆子', +'李連杰' => '李連杰', +'李连杰' => '李連杰', +'材干' => '材幹', +'村庄' => '村莊', +'村落发' => '村落發', +'村里' => '村裡', +'杜老志道' => '杜老誌道', +'杞宋无征' => '杞宋無徵', +'束发' => '束髮', +'杯干' => '杯乾', +'杯面' => '杯麵', +'杰特' => '杰特', +'东岳' => '東嶽', +'东冲西突' => '東衝西突', +'东游' => '東遊', +'松山庄' => '松山庄', +'松柏后凋' => '松柏後凋', +'板着脸' => '板著臉', +'板荡' => '板蕩', +'林宏岳' => '林宏嶽', +'林郁方' => '林郁方', +'林钟' => '林鐘', +'果干' => '果乾', +'果子干' => '果子乾', +'果于' => '果於', +'枝不得大于干' => '枝不得大於榦', +'枝干' => '枝幹', +'枯干' => '枯乾', +'某只' => '某隻', +'染指于' => '染指於', +'染发' => '染髮', +'柜上' => '柜上', +'柜子' => '柜子', +'柜柳' => '柜柳', +'柱梁' => '柱樑', +'柳诒征' => '柳詒徵', +'栖栖皇皇' => '栖栖皇皇', +'校准' => '校準', +'校仇' => '校讎', +'核准的' => '核准的', +'格于' => '格於', +'格范' => '格範', +'格里历' => '格里曆', +'格斗' => '格鬥', +'桂圆干' => '桂圓乾', +'桅杆' => '桅杆', +'案发后' => '案發後', +'桌几' => '桌几', +'桌历' => '桌曆', +'桌历史' => '桌歷史', +'桑干' => '桑乾', +'梁上君子' => '梁上君子', +'条干' => '條幹', +'梨干' => '梨乾', +'梯冲' => '梯衝', +'械系' => '械繫', +'械斗' => '械鬥', +'弃妻女于不顾' => '棄妻女於不顧', +'弃舍' => '棄捨', +'棉制' => '棉製', +'棒子面' => '棒子麵', +'枣庄' => '棗莊', +'栋梁' => '棟樑', +'棫朴' => '棫樸', +'栖于' => '棲於', +'植发' => '植髮', +'椰枣干' => '椰棗乾', +'楚庄问鼎' => '楚莊問鼎', +'楚庄王' => '楚莊王', +'楚庄绝缨' => '楚莊絕纓', +'桢干' => '楨幹', +'业精于勤荒于嬉' => '業精於勤荒於嬉', +'业余' => '業餘', +'榨干' => '榨乾', +'荣登后座' => '榮登后座', +'杠杆' => '槓桿', +'乐意于' => '樂意於', +'乐于' => '樂於', +'樊于期' => '樊於期', +'梁上' => '樑上', +'梁柱' => '樑柱', +'标志着' => '標志著', +'标杆' => '標杆', +'标标致致' => '標標致致', +'标准' => '標準', +'标签' => '標籤', +'标致' => '標緻', +'标注' => '標註', +'标志' => '標誌', +'模棱' => '模稜', +'模范' => '模範', +'模范棒棒堂' => '模范棒棒堂', +'模制' => '模製', +'样范' => '樣範', +'樵采' => '樵採', +'朴修斯' => '樸修斯', +'朴厚' => '樸厚', +'朴学' => '樸學', +'朴实' => '樸實', +'朴念仁' => '樸念仁', +'朴拙' => '樸拙', +'朴樕' => '樸樕', +'朴父' => '樸父', +'朴直' => '樸直', +'朴素' => '樸素', +'朴讷' => '樸訥', +'朴质' => '樸質', +'朴鄙' => '樸鄙', +'朴重' => '樸重', +'朴野' => '樸野', +'朴钝' => '樸鈍', +'朴陋' => '樸陋', +'朴马' => '樸馬', +'朴鲁' => '樸魯', +'树干' => '樹榦', +'树梁' => '樹樑', +'桥梁' => '橋樑', +'机械系' => '機械系', +'机绣' => '機繡', +'横征暴敛' => '橫徵暴斂', +'横杆' => '橫杆', +'横梁' => '橫樑', +'横冲' => '橫衝', +'台子' => '檯子', +'台布' => '檯布', +'台灯' => '檯燈', +'台球' => '檯球', +'台面' => '檯面', +'柜台' => '櫃檯', +'栉发工' => '櫛髮工', +'栏杆' => '欄杆', +'次于' => '次於', +'欲海难填' => '欲海難填', +'欺蒙' => '欺矇', +'歇后' => '歇後', +'歌钟' => '歌鐘', +'欧游' => '歐遊', +'止咳药' => '止咳藥', +'止于' => '止於', +'止痛药' => '止痛藥', +'止血药' => '止血藥', +'正官庄' => '正官庄', +'正后' => '正後', +'正于' => '正於', +'正当着' => '正當著', +'此后' => '此後', +'步步高升' => '步步高升', +'武丑' => '武丑', +'武斗' => '武鬥', +'岁聿云暮' => '歲聿云暮', +'归并' => '歸併', +'归功于' => '歸功於', +'归咎于' => '歸咎於', +'归于' => '歸於', +'归罪于' => '歸罪於', +'归余' => '歸餘', +'歹斗' => '歹鬥', +'死后' => '死後', +'死于' => '死於', +'死胡同' => '死胡同', +'死里求生' => '死裡求生', +'死里逃生' => '死裡逃生', +'殖谷' => '殖穀', +'残肴' => '殘肴', +'残余' => '殘餘', +'僵尸' => '殭屍', +'殷师牛斗' => '殷師牛鬥', +'杀虫药' => '殺蟲藥', +'壳里' => '殼裡', +'殿后' => '殿後', +'殿钟自鸣' => '殿鐘自鳴', +'毁于' => '毀於', +'毁钟为铎' => '毀鐘為鐸', +'殴斗' => '毆鬥', +'母范' => '母範', +'母丑' => '母醜', +'每于' => '每於', +'每每只' => '每每只', +'每只' => '每隻', +'毒药' => '毒藥', +'比划' => '比劃', +'毛坏' => '毛坏', +'毛姜' => '毛薑', +'毛发' => '毛髮', +'毫厘' => '毫釐', +'毫发' => '毫髮', +'气在心里' => '氣在心裡', +'气冲斗牛' => '氣沖斗牛', +'气郁' => '氣鬱', +'氤郁' => '氤鬱', +'水来汤里去' => '水來湯裡去', +'水准' => '水準', +'水里' => '水裡', +'水里鄉' => '水里鄉', +'水里乡' => '水里鄉', +'永历' => '永曆', +'永历史' => '永歷史', +'永志不忘' => '永誌不忘', +'求知欲' => '求知慾', +'求签' => '求籤', +'求道于盲' => '求道於盲', +'池里' => '池裡', +'污蔑' => '污衊', +'汲于' => '汲於', +'决斗' => '決鬥', +'沈淀' => '沈澱', +'沈着' => '沈著', +'沈郁' => '沈鬱', +'沉湎于' => '沉湎於', +'沉淀' => '沉澱', +'沉郁' => '沉鬱', +'没干没净' => '沒乾沒淨', +'没事干' => '沒事幹', +'没干' => '沒幹', +'没折至' => '沒摺至', +'没梢干' => '沒梢幹', +'没样范' => '沒樣範', +'没准' => '沒準', +'没药' => '沒藥', +'冲冠发怒' => '沖冠髮怒', +'沙里淘金' => '沙裡淘金', +'河岳' => '河嶽', +'河流汇集' => '河流匯集', +'河里' => '河裡', +'油斗' => '油鬥', +'油面' => '油麵', +'沿溯' => '沿泝', +'法占' => '法佔', +'法自制' => '法自制', +'泛游' => '泛遊', +'泡制' => '泡製', +'泡面' => '泡麵', +'波棱菜' => '波稜菜', +'波发藻' => '波髮藻', +'泥于' => '泥於', +'注云' => '注云', +'泰山梁木' => '泰山梁木', +'泱郁' => '泱鬱', +'泳气钟' => '泳氣鐘', +'洄游' => '洄遊', +'洋面' => '洋麵', +'洒家' => '洒家', +'洒扫' => '洒掃', +'洒水' => '洒水', +'洒洒' => '洒洒', +'洒淅' => '洒淅', +'洒涤' => '洒滌', +'洒濯' => '洒濯', +'洒然' => '洒然', +'洒脱' => '洒脫', +'洗炼' => '洗鍊', +'洗练' => '洗鍊', +'洗发' => '洗髮', +'洛钟东应' => '洛鐘東應', +'泄欲' => '洩慾', +'洪范' => '洪範', +'洪适' => '洪适', +'洪钟' => '洪鐘', +'汹涌' => '洶湧', +'活动于' => '活動於', +'派团参加' => '派團參加', +'流征' => '流徵', +'流于' => '流於', +'流荡' => '流蕩', +'流风余俗' => '流風餘俗', +'流风余韵' => '流風餘韻', +'浩浩荡荡' => '浩浩蕩蕩', +'浩荡' => '浩蕩', +'浪琴表' => '浪琴錶', +'浪荡' => '浪蕩', +'浪游' => '浪遊', +'浮夸' => '浮夸', +'浮于' => '浮於', +'浮游动物' => '浮游動物', +'浮游植物' => '浮游植物', +'浮游生物' => '浮游生物', +'浮荡' => '浮蕩', +'浮游' => '浮遊', +'浮松' => '浮鬆', +'海干' => '海乾', +'浸于' => '浸於', +'涂壮勋' => '涂壯勳', +'涂壯勳' => '涂壯勳', +'涂謹申' => '涂謹申', +'涂谨申' => '涂謹申', +'涂醒哲' => '涂醒哲', +'涂鸿钦' => '涂鴻欽', +'涂鴻欽' => '涂鴻欽', +'消炎药' => '消炎藥', +'消肿药' => '消腫藥', +'液晶表' => '液晶錶', +'涳蒙' => '涳濛', +'涸干' => '涸乾', +'凉面' => '涼麵', +'淋余土' => '淋餘土', +'淑范' => '淑範', +'泪干' => '淚乾', +'泪如泉涌' => '淚如泉湧', +'淡于' => '淡於', +'淡蒙蒙' => '淡濛濛', +'淡朱' => '淡硃', +'净余' => '淨餘', +'净发' => '淨髮', +'淫欲' => '淫慾', +'淫荡' => '淫蕩', +'淬炼' => '淬鍊', +'深于' => '深於', +'淳朴' => '淳樸', +'渊淳岳峙' => '淵淳嶽峙', +'清心寡欲' => '清心寡欲', +'清汤挂面' => '清湯掛麵', +'减肥药' => '減肥藥', +'渠冲' => '渠衝', +'港制' => '港製', +'游牧民族' => '游牧民族', +'浑朴' => '渾樸', +'浑个' => '渾箇', +'凑合着' => '湊合著', +'湖里' => '湖裡', +'湘绣' => '湘繡', +'湘累' => '湘纍', +'湟潦生苹' => '湟潦生苹', +'涌上' => '湧上', +'涌来' => '湧來', +'涌入' => '湧入', +'涌出' => '湧出', +'涌向' => '湧向', +'涌泉' => '湧泉', +'涌现' => '湧現', +'涌起' => '湧起', +'涌进' => '湧進', +'湮郁' => '湮鬱', +'汤下面' => '湯下麵', +'汤团' => '湯糰', +'汤药' => '湯藥', +'汤面' => '湯麵', +'源于' => '源於', +'准不准' => '準不準', +'准例' => '準例', +'准保' => '準保', +'准备' => '準備', +'准儿' => '準兒', +'准分子' => '準分子', +'准则' => '準則', +'准噶尔' => '準噶爾', +'准定' => '準定', +'准平原' => '準平原', +'准度' => '準度', +'准式' => '準式', +'准拿督' => '準拿督', +'准据' => '準據', +'准拟' => '準擬', +'准新娘' => '準新娘', +'准新郎' => '準新郎', +'准星' => '準星', +'准是' => '準是', +'准时' => '準時', +'准会' => '準會', +'准决赛' => '準決賽', +'准的' => '準的', +'准确' => '準確', +'准线' => '準線', +'准绳' => '準繩', +'准话' => '準話', +'准谱' => '準譜', +'准货币' => '準貨幣', +'准头' => '準頭', +'准点' => '準點', +'溟蒙' => '溟濛', +'溢于' => '溢於', +'溲面' => '溲麵', +'溶于' => '溶於', +'溺于' => '溺於', +'滃郁' => '滃鬱', +'滑借' => '滑藉', +'汇丰' => '滙豐', +'卤味' => '滷味', +'卤水' => '滷水', +'卤汁' => '滷汁', +'卤湖' => '滷湖', +'卤肉' => '滷肉', +'卤菜' => '滷菜', +'卤蛋' => '滷蛋', +'卤制' => '滷製', +'卤鸡' => '滷雞', +'卤面' => '滷麵', +'满于' => '滿於', +'满满当当' => '滿滿當當', +'漂荡' => '漂蕩', +'漕挽' => '漕輓', +'沤郁' => '漚鬱', +'汉弥登钟' => '漢彌登鐘', +'汉弥登钟表公司' => '漢彌登鐘錶公司', +'漫游' => '漫遊', +'潜意识里' => '潛意識裡', +'潜水钟' => '潛水鐘', +'潭里' => '潭裡', +'潮涌' => '潮湧', +'溃于' => '潰於', +'澄澹精致' => '澄澹精致', +'澒蒙' => '澒濛', +'泽渗漓而下降' => '澤滲灕而下降', +'淀粉' => '澱粉', +'澹台' => '澹臺', +'澹荡' => '澹蕩', +'激于' => '激於', +'激荡' => '激蕩', +'浓于' => '濃於', +'浓发' => '濃髮', +'蒙汜' => '濛汜', +'蒙蒙细雨' => '濛濛細雨', +'蒙雾' => '濛霧', +'蒙松雨' => '濛鬆雨', +'蒙鸿' => '濛鴻', +'泻药' => '瀉藥', +'沈吉线' => '瀋吉線', +'沈山线' => '瀋山線', +'沈州' => '瀋州', +'沈水' => '瀋水', +'沈河' => '瀋河', +'沈海' => '瀋海', +'沈海铁路' => '瀋海鐵路', +'沈阳' => '瀋陽', +'濒于' => '瀕於', +'潇洒' => '瀟洒', +'弥山遍野' => '瀰山遍野', +'弥漫' => '瀰漫', +'弥漫着' => '瀰漫著', +'弥弥' => '瀰瀰', +'灌于' => '灌於', +'灌药' => '灌藥', +'漓江' => '灕江', +'漓湘' => '灕湘', +'漓然' => '灕然', +'滩涂' => '灘涂', +'火并非' => '火並非', +'火并' => '火併', +'火拼' => '火拚', +'火折子' => '火摺子', +'火签' => '火籤', +'火药' => '火藥', +'灰蒙' => '灰濛', +'灰蒙蒙' => '灰濛濛', +'炆面' => '炆麵', +'炒面' => '炒麵', +'炮制' => '炮製', +'炸药' => '炸藥', +'炸酱面' => '炸醬麵', +'为后' => '為後', +'为准' => '為準', +'为着' => '為著', +'乌发' => '烏髮', +'乌龙面' => '烏龍麵', +'烘干' => '烘乾', +'烘制' => '烘製', +'烤干' => '烤乾', +'烤晒' => '烤晒', +'焙干' => '焙乾', +'无后' => '無後', +'无征不信' => '無徵不信', +'无业游民' => '無業游民', +'无梁楼盖' => '無樑樓蓋', +'无药可救' => '無藥可救', +'無言不仇' => '無言不讎', +'无余' => '無餘', +'然后' => '然後', +'然身死才数月耳' => '然身死纔數月耳', +'炼药' => '煉藥', +'炼制' => '煉製', +'煎药' => '煎藥', +'煎面' => '煎麵', +'烟卷' => '煙捲', +'烟斗丝' => '煙斗絲', +'照占' => '照佔', +'照入签' => '照入籤', +'照准' => '照準', +'照相干片' => '照相乾片', +'煨干' => '煨乾', +'煮面' => '煮麵', +'荧郁' => '熒鬱', +'熔于' => '熔於', +'熬药' => '熬藥', +'炖药' => '燉藥', +'燎发' => '燎髮', +'烧干' => '燒乾', +'燕几' => '燕几', +'燕巢于幕' => '燕巢於幕', +'燕游' => '燕遊', +'烫发' => '燙髮', +'烫面' => '燙麵', +'营干' => '營幹', +'烬余' => '燼餘', +'争先恐后' => '爭先恐後', +'争名于朝,争利于市' => '爭名於朝,爭利於市', +'争奇斗妍' => '爭奇鬥妍', +'争奇斗异' => '爭奇鬥異', +'争奇斗艳' => '爭奇鬥豔', +'争妍斗奇' => '爭妍鬥奇', +'争妍斗艳' => '爭妍鬥豔', +'争红斗紫' => '爭紅鬥紫', +'争斗' => '爭鬥', +'爰定祥历' => '爰定祥厤', +'爽荡' => '爽蕩', +'尔冬升' => '爾冬陞', +'尔后' => '爾後', +'墙里' => '牆裡', +'片言只语' => '片言隻語', +'牙签' => '牙籤', +'牛后' => '牛後', +'牛肉面' => '牛肉麵', +'牛只' => '牛隻', +'物欲' => '物慾', +'特制住' => '特制住', +'特制定' => '特制定', +'特制止' => '特制止', +'特制订' => '特制訂', +'特征' => '特徵', +'特效药' => '特效藥', +'特于' => '特於', +'特制' => '特製', +'牵一发' => '牽一髮', +'牵挂' => '牽挂', +'牵系' => '牽繫', +'荦确' => '犖确', +'狂占' => '狂佔', +'狂并潮' => '狂併潮', +'狃于' => '狃於', +'狐借虎威' => '狐藉虎威', +'狗嘴里' => '狗嘴裡', +'猛于' => '猛於', +'猛冲' => '猛衝', +'猜三划五' => '猜三划五', +'呆串了皮' => '獃串了皮', +'呆事' => '獃事', +'呆人' => '獃人', +'呆子' => '獃子', +'呆性' => '獃性', +'呆想' => '獃想', +'呆憨呆' => '獃憨獃', +'呆根' => '獃根', +'呆气' => '獃氣', +'呆滞' => '獃滯', +'呆呆' => '獃獃', +'呆痴' => '獃痴', +'呆磕' => '獃磕', +'呆等' => '獃等', +'呆脑' => '獃腦', +'呆着' => '獃著', +'呆话' => '獃話', +'呆头' => '獃頭', +'奖杯' => '獎盃', +'独占' => '獨佔', +'独占鳌头' => '獨佔鰲頭', +'独辟蹊径' => '獨闢蹊徑', +'获匪其丑' => '獲匪其醜', +'兽欲' => '獸慾', +'献丑' => '獻醜', +'率团参加' => '率團參加', +'玉历' => '玉曆', +'玉历史' => '玉歷史', +'王庄' => '王莊', +'王余鱼' => '王餘魚', +'玩弄于股掌之上' => '玩弄於股掌之上', +'珍肴异馔' => '珍肴異饌', +'班里' => '班裡', +'现于' => '現於', +'球杆' => '球杆', +'理发' => '理髮', +'琴钟' => '琴鐘', +'瑞征' => '瑞徵', +'瑶签' => '瑤籤', +'环游' => '環遊', +'瓮安' => '甕安', +'甘于' => '甘於', +'甚于' => '甚於', +'甚么' => '甚麼', +'甜水面' => '甜水麵', +'甜面酱' => '甜麵醬', +'生力面' => '生力麵', +'生于' => '生於', +'生殖洄游' => '生殖洄游', +'生发生' => '生發生', +'生姜' => '生薑', +'生锈' => '生鏽', +'生发' => '生髮', +'产卵洄游' => '產卵洄游', +'产后' => '產後', +'产于' => '產於', +'用于' => '用於', +'用药' => '用藥', +'甩发' => '甩髮', +'田谷' => '田穀', +'田庄' => '田莊', +'田里' => '田裡', +'由余' => '由余', +'由于' => '由於', +'由表及里' => '由表及裡', +'男佣人' => '男佣人', +'男仆' => '男僕', +'男用表' => '男用錶', +'畏于' => '畏於', +'留后' => '留後', +'留发' => '留髮', +'毕于' => '畢於', +'毕业于' => '畢業於', +'毕生发展' => '畢生發展', +'画着' => '畫著', +'异于' => '異於', +'当一天和尚撞一天钟' => '當一天和尚撞一天鐘', +'当一天和尚,撞一天钟' => '當一天和尚,撞一天鐘', +'当家才知柴米价' => '當家纔知柴米價', +'当于' => '當於', +'当准' => '當準', +'当当丁丁' => '當當丁丁', +'当着' => '當著', +'疏于' => '疏於', +'疏松' => '疏鬆', +'疑系' => '疑係', +'疑凶' => '疑兇', +'疲于' => '疲於', +'疲困' => '疲睏', +'病后' => '病後', +'病后初愈' => '病後初癒', +'病征' => '病徵', +'病余' => '病餘', +'症候群' => '症候群', +'痊愈' => '痊癒', +'痒疹' => '痒疹', +'痒痒' => '痒痒', +'痕迹' => '痕迹', +'痴呆症' => '痴呆症', +'痴呆' => '痴獃', +'症候' => '癥候', +'症状' => '癥狀', +'症结' => '癥結', +'癸丑' => '癸丑', +'发干' => '發乾', +'发于' => '發於', +'发汗药' => '發汗藥', +'发呆' => '發獃', +'发蒙' => '發矇', +'发签' => '發籤', +'发庄' => '發莊', +'发着' => '發著', +'发松' => '發鬆', +'发面' => '發麵', +'白干' => '白乾', +'白兔擣药' => '白兔擣藥', +'白干儿' => '白干兒', +'白术' => '白朮', +'白朴' => '白樸', +'白净面皮' => '白淨面皮', +'白发其事' => '白發其事', +'白皮松' => '白皮松', +'白粉面' => '白粉麵', +'白里透红' => '白裡透紅', +'白发' => '白髮', +'白霉' => '白黴', +'百只可' => '百只可', +'百只够' => '百只夠', +'百只怕' => '百只怕', +'百只足够' => '百只足夠', +'百多只' => '百多隻', +'百天后' => '百天後', +'百拙千丑' => '百拙千醜', +'百谷' => '百穀', +'百扎' => '百紮', +'百花历' => '百花曆', +'百花历史' => '百花歷史', +'百药之长' => '百藥之長', +'百炼' => '百鍊', +'百只' => '百隻', +'皆准' => '皆準', +'皇天后土' => '皇天后土', +'皇历' => '皇曆', +'皇极历' => '皇極曆', +'皇极历史' => '皇極歷史', +'皇历史' => '皇歷史', +'皇庄' => '皇莊', +'皓发' => '皓髮', +'皮制服' => '皮制服', +'皮里阳秋' => '皮裏陽秋', +'皮里春秋' => '皮裡春秋', +'皮制' => '皮製', +'皮松' => '皮鬆', +'皱别' => '皺彆', +'皱折' => '皺摺', +'盆吊' => '盆弔', +'盈余' => '盈餘', +'益于' => '益於', +'盒里' => '盒裡', +'盛赞' => '盛讚', +'盗采' => '盜採', +'盗钟' => '盜鐘', +'监制' => '監製', +'盘里' => '盤裡', +'盘回' => '盤迴', +'卢棱伽' => '盧稜伽', +'盲干' => '盲幹', +'直接参与' => '直接參与', +'直于' => '直於', +'直冲' => '直衝', +'相并' => '相併', +'相克制' => '相克制', +'相克服' => '相克服', +'相克' => '相剋', +'相干' => '相干', +'相于' => '相於', +'相冲' => '相衝', +'相斗' => '相鬥', +'看准' => '看準', +'真凶' => '真兇', +'真个' => '真箇', +'眼帘' => '眼帘', +'眼眶里' => '眼眶裡', +'眼药' => '眼藥', +'眼里' => '眼裡', +'困乏' => '睏乏', +'困倦' => '睏倦', +'困觉' => '睏覺', +'睡着了' => '睡著了', +'睡游病' => '睡遊病', +'瞄准' => '瞄準', +'瞠乎后矣' => '瞠乎後矣', +'了望' => '瞭望', +'了然' => '瞭然', +'了若指掌' => '瞭若指掌', +'瞳蒙' => '瞳矇', +'瞻前顾后' => '瞻前顧後', +'蒙事' => '矇事', +'蒙昧无知' => '矇昧無知', +'蒙混' => '矇混', +'蒙瞍' => '矇瞍', +'蒙眬' => '矇矓', +'蒙聩' => '矇聵', +'蒙着' => '矇著', +'蒙着锅儿' => '矇著鍋兒', +'蒙头转' => '矇頭轉', +'蒙骗' => '矇騙', +'瞩托' => '矚託', +'矜庄' => '矜莊', +'短几' => '短几', +'短于' => '短於', +'短发' => '短髮', +'矮几' => '矮几', +'石几' => '石几', +'石家庄' => '石家莊', +'石梁' => '石樑', +'石英表' => '石英錶', +'石莼' => '石蓴', +'石钟乳' => '石鐘乳', +'研制' => '研製', +'砰当' => '砰噹', +'朱唇皓齿' => '硃唇皓齒', +'朱批' => '硃批', +'朱砂' => '硃砂', +'朱笔' => '硃筆', +'朱红色' => '硃紅色', +'朱色' => '硃色', +'朱谕' => '硃諭', +'硬干' => '硬幹', +'确瘠' => '确瘠', +'碑志' => '碑誌', +'碰钟' => '碰鐘', +'磁制' => '磁製', +'磨制' => '磨製', +'磨炼' => '磨鍊', +'硗确' => '磽确', +'碍于' => '礙於', +'碍难照准' => '礙難照准', +'砻谷机' => '礱穀機', +'示范' => '示範', +'社里' => '社裡', +'祝赞' => '祝讚', +'祝发' => '祝髮', +'神荼郁垒' => '神荼鬱壘', +'神游' => '神遊', +'神雕像' => '神雕像', +'神雕' => '神鵰', +'票庄' => '票莊', +'祭吊' => '祭弔', +'祭吊文' => '祭弔文', +'禁欲' => '禁慾', +'禁欲主义' => '禁欲主義', +'禁药' => '禁藥', +'祸于' => '禍於', +'御侮' => '禦侮', +'御寇' => '禦寇', +'御寒' => '禦寒', +'御敌' => '禦敵', +'礼赞' => '禮讚', +'禹余粮' => '禹餘糧', +'禾谷' => '禾穀', +'秃妃之发' => '禿妃之髮', +'秃发' => '禿髮', +'秀发' => '秀髮', +'私下里' => '私下裡', +'私欲' => '私慾', +'私斗' => '私鬥', +'秋天里' => '秋天裡', +'秋后' => '秋後', +'秋裤' => '秋褲', +'秋游' => '秋遊', +'秋阴入井干' => '秋陰入井幹', +'秋发' => '秋髮', +'种师中' => '种師中', +'种师道' => '种師道', +'种放' => '种放', +'科范' => '科範', +'秒表明' => '秒表明', +'秒表示' => '秒表示', +'秒表' => '秒錶', +'秒钟' => '秒鐘', +'移祸于' => '移禍於', +'稀松' => '稀鬆', +'税后' => '稅後', +'税后净利' => '稅後淨利', +'税捐稽征处' => '稅捐稽征處', +'稍后' => '稍後', +'棱台' => '稜台', +'棱子' => '稜子', +'棱层' => '稜層', +'棱柱' => '稜柱', +'棱登' => '稜登', +'棱棱' => '稜稜', +'棱等登' => '稜等登', +'棱线' => '稜線', +'棱缝' => '稜縫', +'棱角' => '稜角', +'棱锥' => '稜錐', +'棱镜' => '稜鏡', +'棱体' => '稜體', +'种谷' => '種穀', +'称赞' => '稱讚', +'稻谷' => '稻穀', +'稽征' => '稽徵', +'谷人' => '穀人', +'谷保家商' => '穀保家商', +'谷仓' => '穀倉', +'谷圭' => '穀圭', +'谷场' => '穀場', +'谷子' => '穀子', +'谷日' => '穀日', +'谷旦' => '穀旦', +'谷梁' => '穀梁', +'谷壳' => '穀殼', +'谷物' => '穀物', +'谷皮' => '穀皮', +'谷神' => '穀神', +'谷谷' => '穀穀', +'谷米' => '穀米', +'谷粒' => '穀粒', +'谷舱' => '穀艙', +'谷苗' => '穀苗', +'谷草' => '穀草', +'谷贵饿农' => '穀貴餓農', +'谷贱伤农' => '穀賤傷農', +'谷道' => '穀道', +'谷雨' => '穀雨', +'谷类' => '穀類', +'谷风' => '穀風', +'谷食' => '穀食', +'穆罕默德历' => '穆罕默德曆', +'穆罕默德历史' => '穆罕默德歷史', +'积极参与' => '積极參与', +'积极参加' => '積极參加', +'积谷' => '積穀', +'积谷防饥' => '積穀防饑', +'积郁' => '積鬱', +'稳占' => '穩佔', +'稳扎' => '穩紮', +'空蒙' => '空濛', +'空荡' => '空蕩', +'空荡荡' => '空蕩蕩', +'空谷回音' => '空谷回音', +'空钟' => '空鐘', +'窒欲' => '窒慾', +'窗台上' => '窗台上', +'窗帘' => '窗帘', +'窗明几亮' => '窗明几亮', +'窗明几净' => '窗明几淨', +'窗台' => '窗檯', +'窝里' => '窩裡', +'穷于' => '窮於', +'穷追不舍' => '窮追不捨', +'穷发' => '窮髮', +'窃钟掩耳' => '竊鐘掩耳', +'立于' => '立於', +'立范' => '立範', +'站干岸儿' => '站乾岸兒', +'竟于' => '竟於', +'童仆' => '童僕', +'端庄' => '端莊', +'竞斗' => '競鬥', +'竹几' => '竹几', +'竹林之游' => '竹林之遊', +'竹签' => '竹籤', +'笑里藏刀' => '笑裡藏刀', +'笨笨呆呆' => '笨笨呆呆', +'笔划' => '筆劃', +'笔秃墨干' => '筆禿墨乾', +'等于' => '等於', +'笋干' => '筍乾', +'筑前' => '筑前', +'筑北' => '筑北', +'筑州' => '筑州', +'筑後' => '筑後', +'筑后' => '筑後', +'筑波' => '筑波', +'筑紫' => '筑紫', +'筑肥' => '筑肥', +'筑西' => '筑西', +'筑邦' => '筑邦', +'筑陽' => '筑陽', +'筑阳' => '筑陽', +'答复' => '答覆', +'答覆' => '答覆', +'策划' => '策劃', +'筵几' => '筵几', +'个中原因' => '箇中原因', +'个中奥妙' => '箇中奧妙', +'个中奥秘' => '箇中奧秘', +'个中好手' => '箇中好手', +'个中强手' => '箇中強手', +'个中消息' => '箇中消息', +'个中滋味' => '箇中滋味', +'个中玄机' => '箇中玄機', +'个中理由' => '箇中理由', +'个中讯息' => '箇中訊息', +'个中资讯' => '箇中資訊', +'个中高手' => '箇中高手', +'个旧' => '箇舊', +'算在里面' => '算在裡面', +'算历' => '算曆', +'算历史' => '算歷史', +'算准' => '算準', +'算发' => '算髮', +'管人吊脚儿事' => '管人弔腳兒事', +'管制法' => '管制法', +'管干' => '管幹', +'节欲' => '節慾', +'节余' => '節餘', +'范例' => '範例', +'范围' => '範圍', +'范式' => '範式', +'范文' => '範文', +'范本' => '範本', +'范畴' => '範疇', +'简并' => '簡併', +'简朴' => '簡樸', +'簸荡' => '簸蕩', +'签着' => '簽著', +'筹划' => '籌劃', +'签幐' => '籤幐', +'签押' => '籤押', +'签条' => '籤條', +'签诗' => '籤詩', +'吁天' => '籲天', +'吁求' => '籲求', +'吁请' => '籲請', +'米谷' => '米穀', +'粉拳绣腿' => '粉拳繡腿', +'粉签子' => '粉籤子', +'粗制' => '粗製', +'精制伏' => '精制伏', +'精制住' => '精制住', +'精制服' => '精制服', +'精干' => '精幹', +'精于' => '精於', +'精准' => '精準', +'精致' => '精緻', +'精制' => '精製', +'精炼' => '精鍊', +'精辟' => '精闢', +'精松' => '精鬆', +'糊里糊涂' => '糊裡糊塗', +'糕干' => '糕乾', +'粪秽蔑面' => '糞穢衊面', +'团子' => '糰子', +'系着' => '系著', +'系里' => '系裡', +'纪元后' => '紀元後', +'纪历' => '紀曆', +'纪历史' => '紀歷史', +'约占' => '約佔', +'红绳系足' => '紅繩繫足', +'红霉素' => '紅霉素', +'红发' => '紅髮', +'纡回' => '紆迴', +'纡余' => '紆餘', +'纡郁' => '紆鬱', +'纳征' => '納徵', +'纯朴' => '純樸', +'纸扎' => '紙紮', +'素朴' => '素樸', +'素发' => '素髮', +'素面' => '素麵', +'索我于枯鱼之肆' => '索我於枯魚之肆', +'索马里' => '索馬里', +'索馬里' => '索馬里', +'索面' => '索麵', +'紫姜' => '紫薑', +'扎上' => '紮上', +'扎下' => '紮下', +'扎囮' => '紮囮', +'扎好' => '紮好', +'扎实' => '紮實', +'扎寨' => '紮寨', +'扎带子' => '紮帶子', +'扎成' => '紮成', +'扎根' => '紮根', +'扎营' => '紮營', +'扎紧' => '紮緊', +'扎脚' => '紮腳', +'扎裹' => '紮裹', +'扎诈' => '紮詐', +'扎起' => '紮起', +'扎铁' => '紮鐵', +'细不容发' => '細不容髮', +'细致' => '細緻', +'细炼' => '細鍊', +'终于' => '終於', +'组里' => '組裡', +'结伴同游' => '結伴同遊', +'结伙' => '結夥', +'结扎' => '結紮', +'结彩' => '結綵', +'结余' => '結餘', +'结发' => '結髮', +'绝对参照' => '絕對參照', +'绝后' => '絕後', +'绝于' => '絕於', +'绞干' => '絞乾', +'给我干脆' => '給我干脆', +'给于' => '給於', +'丝来线去' => '絲來線去', +'丝布' => '絲布', +'丝恩发怨' => '絲恩髮怨', +'丝板' => '絲板', +'丝瓜布' => '絲瓜布', +'丝绒布' => '絲絨布', +'丝线' => '絲線', +'丝织厂' => '絲織廠', +'丝虫' => '絲蟲', +'丝发' => '絲髮', +'绑扎' => '綁紮', +'綑扎' => '綑紮', +'经有云' => '經有云', +'绿发' => '綠髮', +'绸缎庄' => '綢緞莊', +'维系' => '維繫', +'绾发' => '綰髮', +'网里' => '網裡', +'网志' => '網誌', +'彩带' => '綵帶', +'彩排' => '綵排', +'彩楼' => '綵樓', +'彩牌楼' => '綵牌樓', +'彩球' => '綵球', +'彩绸' => '綵綢', +'彩线' => '綵線', +'彩船' => '綵船', +'彩衣' => '綵衣', +'紧致' => '緊緻', +'紧绷' => '緊繃', +'紧绷绷' => '緊繃繃', +'紧绷着' => '緊繃著', +'紧追不舍' => '緊追不捨', +'绪余' => '緒餘', +'緝凶' => '緝兇', +'缉凶' => '緝兇', +'编余' => '編余', +'编制法' => '編制法', +'编采' => '編採', +'编制' => '編製', +'编钟' => '編鐘', +'编发' => '編髮', +'缓征' => '緩徵', +'缓冲' => '緩衝', +'致密' => '緻密', +'萦回' => '縈迴', +'缜致' => '縝緻', +'县里' => '縣裡', +'县志' => '縣誌', +'缝里' => '縫裡', +'缝制' => '縫製', +'缩栗' => '縮慄', +'纵欲' => '縱慾', +'纤夫' => '縴夫', +'纤手' => '縴手', +'总后' => '總後', +'繁复' => '繁複', +'绷住' => '繃住', +'绷子' => '繃子', +'绷带' => '繃帶', +'绷扒吊拷' => '繃扒弔拷', +'绷紧' => '繃緊', +'绷脸' => '繃臉', +'绷着' => '繃著', +'绷着脸' => '繃著臉', +'绷着脸儿' => '繃著臉兒', +'绷开' => '繃開', +'穗帏飘井干' => '繐幃飄井幹', +'绕梁' => '繞樑', +'绣像' => '繡像', +'绣口' => '繡口', +'绣得' => '繡得', +'绣户' => '繡戶', +'绣房' => '繡房', +'绣毯' => '繡毯', +'绣球' => '繡球', +'绣的' => '繡的', +'绣花' => '繡花', +'绣衣' => '繡衣', +'绣起' => '繡起', +'绣阁' => '繡閣', +'绣鞋' => '繡鞋', +'绘制' => '繪製', +'系上' => '繫上', +'系世' => '繫世', +'系到' => '繫到', +'系囚' => '繫囚', +'系心' => '繫心', +'系念' => '繫念', +'系怀' => '繫懷', +'系恋' => '繫戀', +'系于' => '繫於', +'系系' => '繫系', +'系结' => '繫結', +'系紧' => '繫緊', +'系绳' => '繫繩', +'系累' => '繫纍', +'系辞' => '繫辭', +'系风捕影' => '繫風捕影', +'累囚' => '纍囚', +'累堆' => '纍堆', +'累瓦结绳' => '纍瓦結繩', +'累绁' => '纍紲', +'累臣' => '纍臣', +'缠斗' => '纏鬥', +'才则' => '纔則', +'才可容颜十五余' => '纔可容顏十五餘', +'才得两年' => '纔得兩年', +'才此' => '纔此', +'坛子' => '罈子', +'坛坛罐罐' => '罈罈罐罐', +'坛騞' => '罈騞', +'置于' => '置於', +'骂着' => '罵著', +'罢于' => '罷於', +'羁系' => '羈繫', +'美占' => '美佔', +'美仑' => '美崙', +'美于' => '美於', +'美制' => '美製', +'美丑' => '美醜', +'美发' => '美髮', +'羞于' => '羞於', +'群丑' => '群醜', +'羨余' => '羨餘', +'义占' => '義佔', +'义仆' => '義僕', +'义庄' => '義莊', +'习于' => '習於', +'翕辟' => '翕闢', +'翱游' => '翱遊', +'翻涌' => '翻湧', +'翻云覆雨' => '翻雲覆雨', +'翻松' => '翻鬆', +'老干' => '老乾', +'老仆' => '老僕', +'老干部' => '老幹部', +'老蒙' => '老懞', +'老于' => '老於', +'老庄' => '老莊', +'老姜' => '老薑', +'老板' => '老闆', +'老面皮' => '老面皮', +'考后' => '考後', +'考征' => '考徵', +'而后' => '而後', +'而于' => '而於', +'耍斗' => '耍鬥', +'耕佣' => '耕傭', +'耕获' => '耕穫', +'耳后' => '耳後', +'耳余' => '耳餘', +'耽于' => '耽於', +'耿于' => '耿於', +'聊斋志异' => '聊齋志異', +'聘雇' => '聘僱', +'联于' => '聯於', +'联系' => '聯繫', +'听于' => '聽於', +'肉干' => '肉乾', +'肉欲' => '肉慾', +'肉丝面' => '肉絲麵', +'肉羹面' => '肉羹麵', +'肉松' => '肉鬆', +'肚里' => '肚裏', +'肝郁' => '肝鬱', +'股栗' => '股慄', +'肥筑方言' => '肥筑方言', +'肴馔' => '肴饌', +'胃药' => '胃藥', +'胃里' => '胃裡', +'背向着' => '背向著', +'背地里' => '背地裡', +'背后' => '背後', +'胎发' => '胎髮', +'胜肽' => '胜肽', +'胜键' => '胜鍵', +'胡云' => '胡云', +'胡朴安' => '胡樸安', +'胡里胡涂' => '胡裡胡塗', +'能干休' => '能干休', +'能干戈' => '能干戈', +'能干扰' => '能干擾', +'能干政' => '能干政', +'能干涉' => '能干涉', +'能干预' => '能干預', +'能干' => '能幹', +'能自制' => '能自制', +'脉冲' => '脈衝', +'脊梁背' => '脊梁背', +'脊梁骨' => '脊梁骨', +'脊梁' => '脊樑', +'脱谷机' => '脫穀機', +'脱发' => '脫髮', +'腊之以为饵' => '腊之以為餌', +'腊味' => '腊味', +'腊毒' => '腊毒', +'腊笔' => '腊筆', +'腐干' => '腐乾', +'腐余' => '腐餘', +'脑子里' => '腦子裡', +'脑干' => '腦幹', +'脑后' => '腦後', +'腰里' => '腰裡', +'脚注' => '腳註', +'脚炼' => '腳鍊', +'膏药' => '膏藥', +'胶卷' => '膠捲', +'膨松' => '膨鬆', +'臣仆' => '臣僕', +'卧游' => '臥遊', +'臧谷亡羊' => '臧穀亡羊', +'临潼斗宝' => '臨潼鬥寶', +'自制一下' => '自制一下', +'自制下来' => '自制下來', +'自制不' => '自制不', +'自制之力' => '自制之力', +'自制之能' => '自制之能', +'自制他' => '自制他', +'自制伏' => '自制伏', +'自制你' => '自制你', +'自制力' => '自制力', +'自制地' => '自制地', +'自制她' => '自制她', +'自制情' => '自制情', +'自制我' => '自制我', +'自制服' => '自制服', +'自制的能' => '自制的能', +'自制能力' => '自制能力', +'自于' => '自於', +'自制' => '自製', +'自觉自愿' => '自覺自愿', +'至于' => '至於', +'致力于' => '致力於', +'致于' => '致於', +'臻于' => '臻於', +'舂谷' => '舂穀', +'兴致' => '興緻', +'举手表' => '舉手表', +'举手表决' => '舉手表決', +'旧庄' => '舊庄', +'旧历' => '舊曆', +'旧历史' => '舊歷史', +'旧药' => '舊藥', +'旧游' => '舊遊', +'舌干唇焦' => '舌乾唇焦', +'舌后' => '舌後', +'舒卷' => '舒捲', +'航海历' => '航海曆', +'航海历史' => '航海歷史', +'船只得' => '船只得', +'船只有' => '船只有', +'船只能' => '船只能', +'船钟' => '船鐘', +'船只' => '船隻', +'舰只' => '艦隻', +'良药' => '良藥', +'色欲' => '色慾', +'艸木丰丰' => '艸木丰丰', +'芍药' => '芍藥', +'芒果干' => '芒果乾', +'花拳绣腿' => '花拳繡腿', +'花卷' => '花捲', +'花盆里' => '花盆裡', +'花庵词选' => '花菴詞選', +'花药' => '花藥', +'花马吊嘴' => '花馬弔嘴', +'花哄' => '花鬨', +'苑里' => '苑裡', +'若干' => '若干', +'若于' => '若於', +'苦干' => '苦幹', +'苦于' => '苦於', +'苦药' => '苦藥', +'苦里' => '苦裏', +'苦斗' => '苦鬥', +'苎麻' => '苧麻', +'英占' => '英佔', +'苹萦' => '苹縈', +'范登堡' => '范登堡', +'茶几' => '茶几', +'茶庄' => '茶莊', +'茶余' => '茶餘', +'茶面' => '茶麵', +'草丛里' => '草叢裡', +'草广' => '草广', +'草荐' => '草荐', +'草药' => '草藥', +'荐居' => '荐居', +'荐臻' => '荐臻', +'荐饥' => '荐饑', +'荷花淀' => '荷花澱', +'庄上' => '莊上', +'庄主' => '莊主', +'庄周' => '莊周', +'庄员' => '莊員', +'庄严' => '莊嚴', +'庄园' => '莊園', +'庄士顿道' => '莊士頓道', +'庄子' => '莊子', +'庄客' => '莊客', +'庄家' => '莊家', +'庄户' => '莊戶', +'庄房' => '莊房', +'庄敬' => '莊敬', +'庄田' => '莊田', +'庄稼' => '莊稼', +'庄舄越吟' => '莊舄越吟', +'庄里' => '莊裡', +'庄语' => '莊語', +'庄农' => '莊農', +'庄重' => '莊重', +'庄院' => '莊院', +'庄骚' => '莊騷', +'茎干' => '莖幹', +'莽荡' => '莽蕩', +'菌丝体' => '菌絲體', +'菜干' => '菜乾', +'菜肴' => '菜肴', +'菠棱菜' => '菠稜菜', +'菠萝干' => '菠蘿乾', +'华发' => '華髮', +'万一只' => '萬一只', +'万多只' => '萬多隻', +'万天后' => '萬天後', +'万历' => '萬曆', +'万历史' => '萬歷史', +'万签插架' => '萬籤插架', +'万扎' => '萬紮', +'万象' => '萬象', +'万只' => '萬隻', +'落后' => '落後', +'落于' => '落於', +'落发' => '落髮', +'着儿' => '著兒', +'着书立说' => '著書立說', +'着色软体' => '著色軟體', +'着重指出' => '著重指出', +'着录' => '著錄', +'着录规则' => '著錄規則', +'葡占' => '葡佔', +'葡萄干' => '葡萄乾', +'董氏封发' => '董氏封髮', +'葫芦里卖甚么药' => '葫蘆裡賣甚麼藥', +'蒙汗药' => '蒙汗藥', +'蒙庄' => '蒙莊', +'蒙雾露' => '蒙霧露', +'蒜发' => '蒜髮', +'苍术' => '蒼朮', +'苍郁' => '蒼鬱', +'蓄发' => '蓄髮', +'蓄须' => '蓄鬚', +'蓊郁' => '蓊鬱', +'盖于' => '蓋於', +'蓬蓬松松' => '蓬蓬鬆鬆', +'蓬发' => '蓬髮', +'蓬松' => '蓬鬆', +'参绥' => '蔘綏', +'葱郁' => '蔥鬱', +'荞麦面' => '蕎麥麵', +'荡来荡去' => '蕩來蕩去', +'荡女' => '蕩女', +'荡妇' => '蕩婦', +'荡寇' => '蕩寇', +'荡平' => '蕩平', +'荡气回肠' => '蕩氣迴腸', +'荡涤' => '蕩滌', +'荡漾' => '蕩漾', +'荡然' => '蕩然', +'荡产' => '蕩產', +'荡舟' => '蕩舟', +'荡船' => '蕩船', +'荡荡' => '蕩蕩', +'萧参' => '蕭蔘', +'薄幸' => '薄倖', +'薄干' => '薄幹', +'姜是老的辣' => '薑是老的辣', +'姜末' => '薑末', +'姜桂' => '薑桂', +'姜母' => '薑母', +'姜汁' => '薑汁', +'姜汤' => '薑湯', +'姜片' => '薑片', +'姜糖' => '薑糖', +'姜丝' => '薑絲', +'姜老辣' => '薑老辣', +'姜茶' => '薑茶', +'姜蓉' => '薑蓉', +'姜饼' => '薑餅', +'姜黄' => '薑黃', +'薙发' => '薙髮', +'薝卜' => '薝蔔', +'借以' => '藉以', +'借助' => '藉助', +'借寇兵' => '藉寇兵', +'借手' => '藉手', +'借机' => '藉機', +'借此' => '藉此', +'借由' => '藉由', +'借箸代筹' => '藉箸代籌', +'借着' => '藉著', +'借资' => '藉資', +'藏于' => '藏於', +'藏历' => '藏曆', +'藏历史' => '藏歷史', +'藏蒙歌儿' => '藏矇歌兒', +'藤制' => '藤製', +'药丸' => '藥丸', +'药典' => '藥典', +'药到命除' => '藥到命除', +'药到病除' => '藥到病除', +'药剂' => '藥劑', +'药力' => '藥力', +'药包' => '藥包', +'药名' => '藥名', +'药味' => '藥味', +'药品' => '藥品', +'药商' => '藥商', +'药单' => '藥單', +'药婆' => '藥婆', +'药学' => '藥學', +'药害' => '藥害', +'药专' => '藥專', +'药局' => '藥局', +'药师' => '藥師', +'药店' => '藥店', +'药厂' => '藥廠', +'药引' => '藥引', +'药性' => '藥性', +'药房' => '藥房', +'药效' => '藥效', +'药方' => '藥方', +'药材' => '藥材', +'药棉' => '藥棉', +'药检局' => '藥檢局', +'药水' => '藥水', +'药油' => '藥油', +'药液' => '藥液', +'药渣' => '藥渣', +'药片' => '藥片', +'药物' => '藥物', +'药王' => '藥王', +'药理' => '藥理', +'药瓶' => '藥瓶', +'药用' => '藥用', +'药皂' => '藥皂', +'药盒' => '藥盒', +'药石' => '藥石', +'药科' => '藥科', +'药箱' => '藥箱', +'药签' => '藥籤', +'药粉' => '藥粉', +'药糖' => '藥糖', +'药线' => '藥線', +'药罐' => '藥罐', +'药膏' => '藥膏', +'药舖' => '藥舖', +'药茶' => '藥茶', +'药草' => '藥草', +'药行' => '藥行', +'药贩' => '藥販', +'药费' => '藥費', +'药酒' => '藥酒', +'药医学系' => '藥醫學系', +'药量' => '藥量', +'药针' => '藥針', +'药铺' => '藥鋪', +'药头' => '藥頭', +'药饵' => '藥餌', +'药面儿' => '藥麵兒', +'苏昆' => '蘇崑', +'蕴含着' => '蘊含著', +'蕴涵着' => '蘊涵著', +'苹果干' => '蘋果乾', +'萝卜' => '蘿蔔', +'萝卜干' => '蘿蔔乾', +'虎须' => '虎鬚', +'虎斗' => '虎鬥', +'处于' => '處於', +'号志' => '號誌', +'虫部' => '虫部', +'蚊动牛斗' => '蚊動牛鬥', +'蛇发女妖' => '蛇髮女妖', +'蛔虫药' => '蛔蟲藥', +'蜂涌' => '蜂湧', +'蜂准' => '蜂準', +'蜜里调油' => '蜜裡調油', +'蜡月' => '蜡月', +'蜡祭' => '蜡祭', +'虮蝨相吊' => '蟣蝨相弔', +'蛏干' => '蟶乾', +'蠁干' => '蠁幹', +'蛮干' => '蠻幹', +'血拼' => '血拚', +'血余' => '血餘', +'行事历' => '行事曆', +'行事历史' => '行事歷史', +'行凶' => '行兇', +'行凶前' => '行兇前', +'行凶后' => '行兇後', +'行凶後' => '行兇後', +'行于' => '行於', +'行百里者半于九十' => '行百里者半於九十', +'胡同' => '衚衕', +'冲上' => '衝上', +'冲下' => '衝下', +'冲来' => '衝來', +'冲倒' => '衝倒', +'冲冠' => '衝冠', +'冲出' => '衝出', +'冲到' => '衝到', +'冲刺' => '衝刺', +'冲克' => '衝剋', +'冲力' => '衝力', +'冲劲' => '衝勁', +'冲动' => '衝動', +'冲去' => '衝去', +'冲口' => '衝口', +'冲垮' => '衝垮', +'冲堂' => '衝堂', +'冲坚陷阵' => '衝堅陷陣', +'冲压' => '衝壓', +'冲天' => '衝天', +'冲州撞府' => '衝州撞府', +'冲心' => '衝心', +'冲掉' => '衝掉', +'冲撞' => '衝撞', +'冲击' => '衝擊', +'冲散' => '衝散', +'冲杀' => '衝殺', +'冲决' => '衝決', +'冲波' => '衝波', +'冲浪' => '衝浪', +'冲激' => '衝激', +'冲然' => '衝然', +'冲盹' => '衝盹', +'冲破' => '衝破', +'冲程' => '衝程', +'冲突' => '衝突', +'冲线' => '衝線', +'冲着' => '衝著', +'冲要' => '衝要', +'冲起' => '衝起', +'冲车' => '衝車', +'冲进' => '衝進', +'冲过' => '衝過', +'冲量' => '衝量', +'冲锋' => '衝鋒', +'冲陷' => '衝陷', +'冲头阵' => '衝頭陣', +'冲风' => '衝風', +'衣绣昼行' => '衣繡晝行', +'表征' => '表徵', +'表里' => '表裡', +'表面' => '表面', +'衷于' => '衷於', +'袋里' => '袋裡', +'袖里' => '袖裡', +'被里' => '被裡', +'被复' => '被複', +'被覆着' => '被覆著', +'被发佯狂' => '被髮佯狂', +'被发入山' => '被髮入山', +'被发左衽' => '被髮左衽', +'被发缨冠' => '被髮纓冠', +'被发阳狂' => '被髮陽狂', +'裁并' => '裁併', +'裁制' => '裁製', +'里勾外连' => '裏勾外連', +'里手' => '裏手', +'里海' => '裏海', +'里面' => '裏面', +'补于' => '補於', +'补药' => '補藥', +'补血药' => '補血藥', +'补注' => '補註', +'装折' => '裝摺', +'里外' => '裡外', +'里子' => '裡子', +'里屋' => '裡屋', +'里层' => '裡層', +'里布' => '裡布', +'里带' => '裡帶', +'里弦' => '裡弦', +'里应外合' => '裡應外合', +'里脊' => '裡脊', +'里衣' => '裡衣', +'里通外国' => '裡通外國', +'里通外敌' => '裡通外敵', +'里边' => '裡邊', +'里间' => '裡間', +'里面儿' => '裡面兒', +'里面包' => '裡面包', +'里头' => '裡頭', +'制件' => '製件', +'制作' => '製作', +'制做' => '製做', +'制备' => '製備', +'制冰' => '製冰', +'制冷' => '製冷', +'制剂' => '製劑', +'制品' => '製品', +'制图' => '製圖', +'制成' => '製成', +'制法' => '製法', +'制浆' => '製漿', +'制为' => '製為', +'制片' => '製片', +'制版' => '製版', +'制程' => '製程', +'制糖' => '製糖', +'制纸' => '製紙', +'制药' => '製藥', +'制表' => '製表', +'制造' => '製造', +'制革' => '製革', +'制鞋' => '製鞋', +'制盐' => '製鹽', +'复仞年如' => '複仞年如', +'复以百万' => '複以百萬', +'复位' => '複位', +'复信' => '複信', +'复元音' => '複元音', +'复函数' => '複函數', +'复分数' => '複分數', +'复分析' => '複分析', +'复分解反应' => '複分解反應', +'复列' => '複列', +'复利' => '複利', +'复印' => '複印', +'复原' => '複原', +'复句' => '複句', +'复合' => '複合', +'复名' => '複名', +'复员' => '複員', +'复壁' => '複壁', +'复壮' => '複壯', +'复姓' => '複姓', +'复字键' => '複字鍵', +'复审' => '複審', +'复写' => '複寫', +'复平面' => '複平面', +'复式' => '複式', +'复复' => '複復', +'复数' => '複數', +'复本' => '複本', +'复查' => '複查', +'复核' => '複核', +'复检' => '複檢', +'复次' => '複次', +'复比' => '複比', +'复决' => '複決', +'复流' => '複流', +'复测' => '複測', +'复亩珍' => '複畝珍', +'复发' => '複發', +'复目' => '複目', +'复眼' => '複眼', +'复种' => '複種', +'复线' => '複線', +'复习' => '複習', +'复色' => '複色', +'复叶' => '複葉', +'复制' => '複製', +'复诊' => '複診', +'复评' => '複評', +'复词' => '複詞', +'复试' => '複試', +'复课' => '複課', +'复议' => '複議', +'复变函数' => '複變函數', +'复赛' => '複賽', +'复辅音' => '複輔音', +'复述' => '複述', +'复选' => '複選', +'复钱' => '複錢', +'复阅' => '複閱', +'复杂' => '複雜', +'复电' => '複電', +'复音' => '複音', +'复韵' => '複韻', +'褒赞' => '褒讚', +'衬里' => '襯裡', +'西占' => '西佔', +'西元后' => '西元後', +'西岳' => '西嶽', +'西晒' => '西晒', +'西历' => '西曆', +'西历史' => '西歷史', +'西药' => '西藥', +'西谷米' => '西谷米', +'西游' => '西遊', +'要占' => '要佔', +'要占卜' => '要占卜', +'要自制' => '要自制', +'要冲' => '要衝', +'要么' => '要麼', +'覆亡' => '覆亡', +'覆命' => '覆命', +'覆巢之下无完卵' => '覆巢之下無完卵', +'覆水难收' => '覆水難收', +'覆没' => '覆沒', +'覆着' => '覆著', +'覆盖' => '覆蓋', +'覆盖着' => '覆蓋著', +'覆辙' => '覆轍', +'覆雨翻云' => '覆雨翻雲', +'见于' => '見於', +'见棱见角' => '見稜見角', +'见素抱朴' => '見素抱樸', +'见钟不打' => '見鐘不打', +'规划' => '規劃', +'规范' => '規範', +'視如寇仇' => '視如寇讎', +'视于' => '視於', +'观采' => '觀採', +'角落发' => '角落發', +'角落里' => '角落裡', +'觚棱' => '觚稜', +'解雇' => '解僱', +'解痛药' => '解痛藥', +'解药' => '解藥', +'解铃仍须系铃人' => '解鈴仍須繫鈴人', +'解铃还须系铃人' => '解鈴還須繫鈴人', +'解发佯狂' => '解髮佯狂', +'触须' => '觸鬚', +'言云' => '言云', +'言大而夸' => '言大而夸', +'言辩而确' => '言辯而确', +'订于' => '訂於', +'订制' => '訂製', +'计划' => '計劃', +'托了' => '託了', +'托事' => '託事', +'托交' => '託交', +'托人' => '託人', +'托付' => '託付', +'托儿所' => '託兒所', +'托古讽今' => '託古諷今', +'托名' => '託名', +'托命' => '託命', +'托咎' => '託咎', +'托梦' => '託夢', +'托大' => '託大', +'托孤' => '託孤', +'托庇' => '託庇', +'托故' => '託故', +'托疾' => '託疾', +'托病' => '託病', +'托福' => '託福', +'托管' => '託管', +'托言' => '託言', +'托词' => '託詞', +'托买' => '託買', +'托卖' => '託賣', +'托身' => '託身', +'托辞' => '託辭', +'托运' => '託運', +'托过' => '託過', +'托附' => '託附', +'设于' => '設於', +'许愿起经' => '許愿起經', +'诉说着' => '訴說著', +'注上' => '註上', +'注册' => '註冊', +'注失' => '註失', +'注定' => '註定', +'注明' => '註明', +'注标' => '註標', +'注生娘娘' => '註生娘娘', +'注疏' => '註疏', +'注脚' => '註腳', +'注解' => '註解', +'注记' => '註記', +'注译' => '註譯', +'注释' => '註釋', +'注销' => '註銷', +'评注' => '評註', +'词干' => '詞幹', +'词汇' => '詞彙', +'词余' => '詞餘', +'询于' => '詢於', +'询于刍荛' => '詢於芻蕘', +'试药' => '試藥', +'试制' => '試製', +'诗云' => '詩云', +'诗赞' => '詩讚', +'诗钟' => '詩鐘', +'诗余' => '詩餘', +'话里有话' => '話裡有話', +'该于' => '該於', +'详征博引' => '詳徵博引', +'详注' => '詳註', +'诔赞' => '誄讚', +'夸多斗靡' => '誇多鬥靡', +'夸能斗智' => '誇能鬥智', +'夸赞' => '誇讚', +'志哀' => '誌哀', +'志喜' => '誌喜', +'志庆' => '誌慶', +'志异' => '誌異', +'认准' => '認準', +'诱奸' => '誘姦', +'语云' => '語云', +'语汇' => '語彙', +'语有云' => '語有云', +'诚征' => '誠徵', +'诚朴' => '誠樸', +'诬蔑' => '誣衊', +'说着' => '說著', +'课后' => '課後', +'课征' => '課徵', +'课余' => '課餘', +'调准' => '調準', +'调制' => '調製', +'谈征' => '談徵', +'请参阅' => '請參閱', +'请君入瓮' => '請君入甕', +'请托' => '請託', +'咨询' => '諮詢', +'诸余' => '諸餘', +'谋定后动' => '謀定後動', +'谋干' => '謀幹', +'谢绝参观' => '謝絕參觀', +'谬采虚声' => '謬採虛聲', +'谬赞' => '謬讚', +'謷丑' => '謷醜', +'谨于心' => '謹於心', +'证于' => '證於', +'警世钟' => '警世鐘', +'警钟' => '警鐘', +'译注' => '譯註', +'护发' => '護髮', +'读后' => '讀後', +'变征' => '變徵', +'变丑' => '變醜', +'仇問' => '讎問', +'仇夷' => '讎夷', +'仇校' => '讎校', +'仇正' => '讎正', +'仇隙' => '讎隙', +'赞不绝口' => '讚不絕口', +'赞佩' => '讚佩', +'赞呗' => '讚唄', +'赞叹不已' => '讚嘆不已', +'赞扬' => '讚揚', +'赞乐' => '讚樂', +'赞歌' => '讚歌', +'赞叹' => '讚歎', +'赞美' => '讚美', +'赞羨' => '讚羨', +'赞许' => '讚許', +'赞词' => '讚詞', +'赞誉' => '讚譽', +'赞赏' => '讚賞', +'赞辞' => '讚辭', +'赞颂' => '讚頌', +'豆干' => '豆乾', +'豆腐干' => '豆腐乾', +'竖着' => '豎著', +'竖起脊梁' => '豎起脊梁', +'丰滨' => '豐濱', +'丰滨乡' => '豐濱鄉', +'象征' => '象徵', +'象征着' => '象徵著', +'负债累累' => '負債纍纍', +'贪欲' => '貪慾', +'贵价' => '貴价', +'贵干' => '貴幹', +'贵征' => '貴徵', +'買凶' => '買兇', +'买凶' => '買兇', +'费占' => '費佔', +'贻范' => '貽範', +'资金占用' => '資金占用', +'贾后' => '賈後', +'赈饥' => '賑饑', +'赏赞' => '賞讚', +'卖呆' => '賣獃', +'质朴' => '質樸', +'赌台' => '賭檯', +'赌斗' => '賭鬥', +'赖于' => '賴於', +'賸余' => '賸餘', +'购并' => '購併', +'购买欲' => '購買慾', +'赢余' => '贏餘', +'赤绳系足' => '赤繩繫足', +'赤霉素' => '赤霉素', +'走回路' => '走回路', +'走后' => '走後', +'起于' => '起於', +'起复' => '起複', +'起哄' => '起鬨', +'超级杯' => '超級盃', +'赶制' => '趕製', +'赶面棍' => '趕麵棍', +'赵庄' => '趙莊', +'趋于' => '趨於', +'趱干' => '趲幹', +'足于' => '足於', +'跌扑' => '跌扑', +'跌荡' => '跌蕩', +'跟前跟后' => '跟前跟後', +'路签' => '路籤', +'跳梁小丑' => '跳樑小丑', +'跳荡' => '跳蕩', +'跳表' => '跳錶', +'蹪于' => '蹪於', +'蹭棱子' => '蹭稜子', +'躁郁' => '躁鬱', +'身后' => '身後', +'身于' => '身於', +'身体发肤' => '身體髮膚', +'躯干' => '軀幹', +'车库里' => '車庫裡', +'车站里' => '車站裡', +'车里' => '車裡', +'轨范' => '軌範', +'轩辟' => '軒闢', +'较于' => '較於', +'载于' => '載於', +'挽曲' => '輓曲', +'挽歌' => '輓歌', +'挽聯' => '輓聯', +'挽联' => '輓聯', +'挽詞' => '輓詞', +'挽词' => '輓詞', +'挽诗' => '輓詩', +'挽詩' => '輓詩', +'轻于' => '輕於', +'轻轻松松' => '輕輕鬆鬆', +'轻松' => '輕鬆', +'轮奸' => '輪姦', +'轮回' => '輪迴', +'转向往' => '轉向往', +'转台' => '轉檯', +'转托' => '轉託', +'转斗千里' => '轉鬥千里', +'辛丑' => '辛丑', +'辟谷' => '辟穀', +'办公台' => '辦公檯', +'辞汇' => '辭彙', +'辫发' => '辮髮', +'辩斗' => '辯鬥', +'农历' => '農曆', +'农历史' => '農歷史', +'农民历' => '農民曆', +'农民历史' => '農民歷史', +'农庄' => '農莊', +'农药' => '農藥', +'迂回' => '迂迴', +'近于' => '近於', +'近日無仇' => '近日無讎', +'近日里' => '近日裡', +'返朴' => '返樸', +'迥然回异' => '迥然迴異', +'迫于' => '迫於', +'回光返照' => '迴光返照', +'回向' => '迴向', +'回圈' => '迴圈', +'回廊' => '迴廊', +'回形夹' => '迴形夾', +'回文' => '迴文', +'回旋' => '迴旋', +'回流' => '迴流', +'回环' => '迴環', +'回纹针' => '迴紋針', +'回绕' => '迴繞', +'回翔' => '迴翔', +'回肠' => '迴腸', +'回诵' => '迴誦', +'回路' => '迴路', +'回转' => '迴轉', +'回递性' => '迴遞性', +'回避' => '迴避', +'回銮' => '迴鑾', +'回音' => '迴音', +'回响' => '迴響', +'回风' => '迴風', +'迷幻药' => '迷幻藥', +'迷于' => '迷於', +'迷蒙' => '迷濛', +'迷药' => '迷藥', +'迷魂药' => '迷魂藥', +'追凶' => '追兇', +'退伙' => '退夥', +'退后' => '退後', +'退烧药' => '退燒藥', +'退藏于密' => '退藏於密', +'逋发' => '逋髮', +'逍遥游' => '逍遙遊', +'透辟' => '透闢', +'这伙人' => '這夥人', +'这里' => '這裏', +'这里在' => '這裡在', +'这里是' => '這裡是', +'这里会' => '這裡會', +'这里有' => '這裡有', +'这里能' => '這裡能', +'这只' => '這隻', +'这么' => '這麼', +'这么着' => '這麼著', +'通奸' => '通姦', +'通心面' => '通心麵', +'通于' => '通於', +'通历' => '通曆', +'通历史' => '通歷史', +'通庄' => '通莊', +'逞凶鬥狠' => '逞兇鬥狠', +'逞凶斗狠' => '逞兇鬥狠', +'造曲' => '造麯', +'连三并四' => '連三併四', +'连占' => '連佔', +'连采' => '連採', +'连于' => '連於', +'连系' => '連繫', +'连庄' => '連莊', +'周游世界' => '週遊世界', +'进占' => '進佔', +'逼并' => '逼併', +'游了' => '遊了', +'游人' => '遊人', +'游仙' => '遊仙', +'游伴' => '遊伴', +'游侠' => '遊俠', +'游冶' => '遊冶', +'游刃有余' => '遊刃有餘', +'游动' => '遊動', +'游园' => '遊園', +'游子' => '遊子', +'游学' => '遊學', +'游客' => '遊客', +'游宦' => '遊宦', +'游山玩水' => '遊山玩水', +'游必有方' => '遊必有方', +'游憩' => '遊憩', +'游戏' => '遊戲', +'游手好闲' => '遊手好閒', +'游方' => '遊方', +'游星' => '遊星', +'游乐' => '遊樂', +'游标卡尺' => '遊標卡尺', +'游历' => '遊歷', +'游民' => '遊民', +'游河' => '遊河', +'游牧' => '遊牧', +'游猎' => '遊獵', +'游玩' => '遊玩', +'游荡' => '遊盪', +'游目骋怀' => '遊目騁懷', +'游程' => '遊程', +'游丝' => '遊絲', +'游兴' => '遊興', +'游船' => '遊船', +'游艇' => '遊艇', +'游荡不归' => '遊蕩不歸', +'游艺' => '遊藝', +'游行' => '遊行', +'游街' => '遊街', +'游览' => '遊覽', +'游记' => '遊記', +'游说' => '遊說', +'游资' => '遊資', +'游走' => '遊走', +'游踪' => '遊蹤', +'游逛' => '遊逛', +'游错' => '遊錯', +'游离' => '遊離', +'游骑兵' => '遊騎兵', +'游魂' => '遊魂', +'遍于' => '遍於', +'过后' => '過後', +'过于' => '過於', +'过杆' => '過杆', +'过水面' => '過水麵', +'道范' => '道範', +'逊于' => '遜於', +'递回' => '遞迴', +'远于' => '遠於', +'远县才至' => '遠縣纔至', +'远游' => '遠遊', +'遨游' => '遨遊', +'适于' => '適於', +'遮丑' => '遮醜', +'迁于' => '遷於', +'遗范' => '遺範', +'遗迹' => '遺迹', +'辽沈' => '遼瀋', +'避孕药' => '避孕藥', +'避暑山庄' => '避暑山庄', +'邀天之幸' => '邀天之倖', +'还占' => '還佔', +'还采' => '還採', +'还于' => '還於', +'还冲' => '還衝', +'邋里邋遢' => '邋裡邋遢', +'那只是' => '那只是', +'那只有' => '那只有', +'那卷' => '那捲', +'那里' => '那裡', +'那只' => '那隻', +'那么' => '那麼', +'那么着' => '那麼著', +'郁朴' => '郁樸', +'郁郁菲菲' => '郁郁菲菲', +'郊游' => '郊遊', +'郘钟' => '郘鐘', +'部落发' => '部落發', +'都于' => '都於', +'乡愿' => '鄉愿', +'邓后' => '鄧後', +'鄭凱云' => '鄭凱云', +'郑凯云' => '鄭凱云', +'郑庄公' => '鄭莊公', +'配制饲料' => '配制飼料', +'配合着' => '配合著', +'配水干管' => '配水幹管', +'配药' => '配藥', +'配制' => '配製', +'酒帘' => '酒帘', +'酒后' => '酒後', +'酒坛' => '酒罈', +'酒肴' => '酒肴', +'酒药' => '酒藥', +'酒醴曲蘖' => '酒醴麴櫱', +'酒曲' => '酒麴', +'酥松' => '酥鬆', +'醇朴' => '醇樸', +'醉于' => '醉於', +'醋坛' => '醋罈', +'丑丫头' => '醜丫頭', +'丑事' => '醜事', +'丑人' => '醜人', +'丑侪' => '醜儕', +'丑八怪' => '醜八怪', +'丑剌剌' => '醜剌剌', +'丑剧' => '醜劇', +'丑化' => '醜化', +'丑史' => '醜史', +'丑名' => '醜名', +'丑咤' => '醜吒', +'丑地' => '醜地', +'丑夷' => '醜夷', +'丑女' => '醜女', +'丑女效颦' => '醜女效顰', +'丑奴儿' => '醜奴兒', +'丑妇' => '醜婦', +'丑媳' => '醜媳', +'丑媳妇' => '醜媳婦', +'丑小鸭' => '醜小鴨', +'丑巴怪' => '醜巴怪', +'丑徒' => '醜徒', +'丑恶' => '醜惡', +'丑态' => '醜態', +'丑毙了' => '醜斃了', +'丑于' => '醜於', +'丑末' => '醜末', +'丑样' => '醜樣', +'丑死' => '醜死', +'丑比' => '醜比', +'丑沮' => '醜沮', +'丑男' => '醜男', +'丑闻' => '醜聞', +'丑声' => '醜聲', +'丑声远播' => '醜聲遠播', +'丑脸' => '醜臉', +'丑虏' => '醜虜', +'丑行' => '醜行', +'丑言' => '醜言', +'丑诋' => '醜詆', +'丑话' => '醜話', +'丑语' => '醜語', +'丑贼生' => '醜賊生', +'丑辞' => '醜辭', +'丑辱' => '醜辱', +'丑逆' => '醜逆', +'丑丑' => '醜醜', +'丑陋' => '醜陋', +'丑杂' => '醜雜', +'丑头怪脸' => '醜頭怪臉', +'丑类' => '醜類', +'酝酿着' => '醞釀著', +'医药' => '醫藥', +'酿制' => '釀製', +'衅钟' => '釁鐘', +'采石之役' => '采石之役', +'采石之戰' => '采石之戰', +'采石之战' => '采石之戰', +'采石磯' => '采石磯', +'采石矶' => '采石磯', +'釉药' => '釉藥', +'里程表' => '里程錶', +'重划' => '重劃', +'重折' => '重摺', +'重于' => '重於', +'重罗面' => '重羅麵', +'重制' => '重製', +'重复' => '重複', +'重托' => '重託', +'重游' => '重遊', +'重锤' => '重鎚', +'野姜' => '野薑', +'野游' => '野遊', +'厘出' => '釐出', +'厘升' => '釐升', +'厘定' => '釐定', +'厘正' => '釐正', +'厘清' => '釐清', +'厘订' => '釐訂', +'金仆姑' => '金僕姑', +'金仑溪' => '金崙溪', +'金布道' => '金布道', +'金表情' => '金表情', +'金表态' => '金表態', +'金表扬' => '金表揚', +'金表明' => '金表明', +'金表演' => '金表演', +'金表现' => '金表現', +'金表示' => '金表示', +'金表达' => '金表達', +'金表露' => '金表露', +'金表面' => '金表面', +'金装玉里' => '金裝玉裡', +'金表' => '金錶', +'金钟' => '金鐘', +'金马仑道' => '金馬崙道', +'金发' => '金髮', +'钉锤' => '釘鎚', +'铃响后' => '鈴響後', +'钩心斗角' => '鉤心鬥角', +'银朱' => '銀硃', +'银发' => '銀髮', +'铜制' => '銅製', +'铜钟' => '銅鐘', +'铝制' => '鋁製', +'铺锦列绣' => '鋪錦列繡', +'钢梁' => '鋼樑', +'钢制' => '鋼製', +'录着' => '錄著', +'录制' => '錄製', +'锤炼' => '錘鍊', +'钱谷' => '錢穀', +'钱庄' => '錢莊', +'锦绣花园' => '錦綉花園', +'锦绣' => '錦繡', +'表带' => '錶帶', +'表店' => '錶店', +'表厂' => '錶廠', +'表板' => '錶板', +'表壳' => '錶殼', +'表盘' => '錶盤', +'表蒙子' => '錶蒙子', +'表针' => '錶針', +'表链' => '錶鏈', +'炼冶' => '鍊冶', +'炼句' => '鍊句', +'炼字' => '鍊字', +'炼师' => '鍊師', +'炼度' => '鍊度', +'炼形' => '鍊形', +'炼气' => '鍊氣', +'炼汞' => '鍊汞', +'炼石' => '鍊石', +'炼贫' => '鍊貧', +'炼金' => '鍊金', +'炼钢' => '鍊鋼', +'锅庄' => '鍋莊', +'锻炼出' => '鍛鍊出', +'锲而不舍' => '鍥而不捨', +'钟表' => '鍾錶', +'镰仓' => '鎌倉', +'锤儿' => '鎚兒', +'锤子' => '鎚子', +'锤头' => '鎚頭', +'锈病' => '鏽病', +'锈菌' => '鏽菌', +'锈蚀' => '鏽蝕', +'钟不扣不鸣' => '鐘不扣不鳴', +'钟不撞不鸣' => '鐘不撞不鳴', +'钟乳洞' => '鐘乳洞', +'钟乳石' => '鐘乳石', +'钟在寺里' => '鐘在寺里', +'钟塔' => '鐘塔', +'钟山' => '鐘山', +'钟形虫' => '鐘形蟲', +'钟摆' => '鐘擺', +'钟楼' => '鐘樓', +'钟漏' => '鐘漏', +'钟琴' => '鐘琴', +'钟相' => '鐘相', +'钟磬' => '鐘磬', +'钟声' => '鐘聲', +'钟表店' => '鐘錶店', +'钟关' => '鐘關', +'钟响' => '鐘響', +'钟头' => '鐘頭', +'钟鸣' => '鐘鳴', +'钟点' => '鐘點', +'钟鼎' => '鐘鼎', +'钟鼓' => '鐘鼓', +'铁杆' => '鐵杆', +'铁栏杆' => '鐵欄杆', +'铁锤' => '鐵鎚', +'铁锈' => '鐵鏽', +'铸钟' => '鑄鐘', +'鉴于' => '鑒於', +'长几' => '長几', +'长于' => '長於', +'长历' => '長曆', +'长历史' => '長歷史', +'长生药' => '長生藥', +'门前门后' => '門前門後', +'门帘' => '門帘', +'门吊儿' => '門弔兒', +'门里' => '門裡', +'开吊' => '開弔', +'开征' => '開徵', +'开采' => '開採', +'开药' => '開藥', +'开辟' => '開闢', +'开哄' => '開鬨', +'闲情逸致' => '閒情逸緻', +'闲荡' => '閒蕩', +'闲游' => '閒遊', +'间不容发' => '間不容髮', +'闵采尔' => '閔採爾', +'合府' => '閤府', +'闺范' => '閨範', +'阃范' => '閫範', +'闯荡' => '闖蕩', +'闯炼' => '闖鍊', +'关系' => '關係', +'关系着' => '關係著', +'关弓与我确' => '關弓與我确', +'关于' => '關於', +'辟佛' => '闢佛', +'辟作' => '闢作', +'辟划' => '闢劃', +'辟土' => '闢土', +'辟地' => '闢地', +'辟室' => '闢室', +'辟建' => '闢建', +'辟为' => '闢為', +'辟田' => '闢田', +'辟筑' => '闢築', +'辟谣' => '闢謠', +'辟辟' => '闢辟', +'辟邪以律' => '闢邪以律', +'防患于未然' => '防患於未然', +'防晒' => '防晒', +'防御' => '防禦', +'防范' => '防範', +'防锈' => '防鏽', +'防台' => '防颱', +'阻于' => '阻於', +'阿呆瓜' => '阿呆瓜', +'阿呆' => '阿獃', +'附于' => '附於', +'附注' => '附註', +'降压药' => '降壓藥', +'降于' => '降於', +'限于' => '限於', +'升官' => '陞官', +'除臭药' => '除臭藥', +'陪吊' => '陪弔', +'阴干' => '陰乾', +'阴历' => '陰曆', +'阴历史' => '陰歷史', +'阴沟里翻船' => '陰溝裡翻船', +'阴郁' => '陰鬱', +'陈炼' => '陳鍊', +'陷于' => '陷於', +'陆游' => '陸遊', +'阳春面' => '陽春麵', +'阳历' => '陽曆', +'阳历史' => '陽歷史', +'隆准许' => '隆准許', +'隆准' => '隆準', +'随后' => '隨後', +'随于' => '隨於', +'隐占' => '隱佔', +'隐几' => '隱几', +'隐于' => '隱於', +'只字' => '隻字', +'只影' => '隻影', +'只手遮天' => '隻手遮天', +'只眼' => '隻眼', +'只言片语' => '隻言片語', +'只身' => '隻身', +'雄斗斗' => '雄斗斗', +'雅范' => '雅範', +'雅致' => '雅緻', +'集于' => '集於', +'集游法' => '集遊法', +'雇佣' => '雇傭', +'雇于' => '雇於', +'雕梁画栋' => '雕樑畫棟', +'双折' => '雙摺', +'双胜类' => '雙胜類', +'双雕' => '雙鵰', +'杂合面儿' => '雜合麵兒', +'杂志' => '雜誌', +'杂面' => '雜麵', +'鸡吵鹅斗' => '雞吵鵝鬥', +'鸡奸' => '雞姦', +'鸡争鹅斗' => '雞爭鵝鬥', +'鸡丝' => '雞絲', +'鸡丝面' => '雞絲麵', +'鸡腿面' => '雞腿麵', +'鸡蛋里挑骨头' => '雞蛋裡挑骨頭', +'鸡只' => '雞隻', +'离于' => '離於', +'难舍' => '難捨', +'难于' => '難於', +'雨后' => '雨後', +'雪窗萤几' => '雪窗螢几', +'雪里' => '雪裡', +'雪里红' => '雪里紅', +'云南白药' => '雲南白藥', +'云笈七签' => '雲笈七籤', +'云游' => '雲遊', +'云须' => '雲鬚', +'零多只' => '零多隻', +'零天后' => '零天後', +'零只' => '零隻', +'零余' => '零餘', +'电子表' => '電子錶', +'电子钟' => '電子鐘', +'电杆' => '電杆', +'电线杆' => '電線杆', +'电冲' => '電衝', +'电表' => '電錶', +'电钟' => '電鐘', +'震栗' => '震慄', +'震于' => '震於', +'震荡' => '震蕩', +'雾里' => '霧裡', +'露丑' => '露醜', +'霸占' => '霸佔', +'霁范' => '霽範', +'灵药' => '靈藥', +'青山一发' => '青山一髮', +'青苹' => '青苹', +'青苹果' => '青蘋果', +'青蝇吊客' => '青蠅弔客', +'青霉素' => '青霉素', +'青霉' => '青黴', +'非占不可' => '非佔不可', +'非于' => '非於', +'靠后' => '靠後', +'靠里面' => '靠裡面', +'面包住' => '面包住', +'面包含' => '面包含', +'面包围' => '面包圍', +'面包容' => '面包容', +'面包庇' => '面包庇', +'面包厢' => '面包廂', +'面包抄' => '面包抄', +'面包括' => '面包括', +'面包揽' => '面包攬', +'面包涵' => '面包涵', +'面包管' => '面包管', +'面包扎' => '面包紮', +'面包罗' => '面包羅', +'面包着' => '面包著', +'面包藏' => '面包藏', +'面包装' => '面包裝', +'面包裹' => '面包裹', +'面包起' => '面包起', +'面包办' => '面包辦', +'面店舖' => '面店舖', +'面朝着' => '面朝著', +'面条目' => '面條目', +'面條目' => '面條目', +'面粉碎' => '面粉碎', +'面粉红' => '面粉紅', +'面临着' => '面臨著', +'面食饭' => '面食飯', +'面食面' => '面食麵', +'鞋里' => '鞋裡', +'鞣制' => '鞣製', +'秋千' => '鞦韆', +'鞭辟入里' => '鞭辟入裡', +'韦庄' => '韋莊', +'韩国制' => '韓國製', +'韩制' => '韓製', +'音准' => '音準', +'音声如钟' => '音聲如鐘', +'韶山冲' => '韶山衝', +'页面' => '頁面', +'頁面' => '頁面', +'项庄' => '項莊', +'顺于' => '順於', +'颂系' => '頌繫', +'颂赞' => '頌讚', +'预制' => '預製', +'领袖欲' => '領袖慾', +'头巾吊在水里' => '頭巾弔在水裡', +'头里' => '頭裡', +'头发' => '頭髮', +'颊须' => '頰鬚', +'题签' => '題籤', +'额征' => '額徵', +'额我略历' => '額我略曆', +'额我略历史' => '額我略歷史', +'颜范' => '顏範', +'颠干倒坤' => '顛乾倒坤', +'颠覆' => '顛覆', +'颠颠仆仆' => '顛顛仆仆', +'顾前不顾后' => '顧前不顧後', +'颤栗' => '顫慄', +'显着标志' => '顯著標志', +'风干' => '風乾', +'风土志' => '風土誌', +'风卷残云' => '風捲殘雲', +'风物志' => '風物誌', +'风范' => '風範', +'风里' => '風裡', +'风起云涌' => '風起雲湧', +'台风' => '颱風', +'刮了' => '颳了', +'刮倒' => '颳倒', +'刮去' => '颳去', +'刮得' => '颳得', +'刮走' => '颳走', +'刮起' => '颳起', +'刮雪' => '颳雪', +'刮风' => '颳風', +'飘荡' => '飄蕩', +'飘游' => '飄遊', +'飘飘荡荡' => '飄飄蕩蕩', +'飞扎' => '飛紮', +'飞刍挽粟' => '飛芻輓粟', +'食欲' => '食慾', +'食欲不振' => '食欲不振', +'食野之苹' => '食野之苹', +'食面' => '食麵', +'饭后' => '飯後', +'饭后钟' => '飯後鐘', +'饭团' => '飯糰', +'饭庄' => '飯莊', +'饲喂' => '飼餵', +'饼干' => '餅乾', +'馂余' => '餕餘', +'余0' => '餘0', +'余1' => '餘1', +'余2' => '餘2', +'余3' => '餘3', +'余4' => '餘4', +'余5' => '餘5', +'余6' => '餘6', +'余7' => '餘7', +'余8' => '餘8', +'余9' => '餘9', +'余〇' => '餘〇', +'余一' => '餘一', +'余七' => '餘七', +'余三' => '餘三', +'余下' => '餘下', +'余九' => '餘九', +'余事' => '餘事', +'余二' => '餘二', +'余五' => '餘五', +'余人' => '餘人', +'余俗' => '餘俗', +'余倍' => '餘倍', +'余僇' => '餘僇', +'余光' => '餘光', +'余八' => '餘八', +'余六' => '餘六', +'余刃' => '餘刃', +'余切' => '餘切', +'余利' => '餘利', +'余割' => '餘割', +'余力' => '餘力', +'余勇' => '餘勇', +'余十' => '餘十', +'余味' => '餘味', +'余喘' => '餘喘', +'余四' => '餘四', +'余地' => '餘地', +'余墨' => '餘墨', +'余外' => '餘外', +'余妙' => '餘妙', +'余姚' => '餘姚', +'余威' => '餘威', +'余子' => '餘子', +'余存' => '餘存', +'余孽' => '餘孽', +'余弦' => '餘弦', +'余思' => '餘思', +'余悸' => '餘悸', +'余庆' => '餘慶', +'余数' => '餘數', +'余明' => '餘明', +'余映' => '餘映', +'余暇' => '餘暇', +'余晖' => '餘暉', +'余杭' => '餘杭', +'余杯' => '餘杯', +'余桃' => '餘桃', +'余桶' => '餘桶', +'余业' => '餘業', +'余款' => '餘款', +'余步' => '餘步', +'余殃' => '餘殃', +'余毒' => '餘毒', +'余气' => '餘氣', +'余波' => '餘波', +'余波荡漾' => '餘波盪漾', +'余温' => '餘溫', +'余泽' => '餘澤', +'余沥' => '餘瀝', +'余烈' => '餘烈', +'余热' => '餘熱', +'余烬' => '餘燼', +'余珍' => '餘珍', +'余生' => '餘生', +'余众' => '餘眾', +'余窍' => '餘竅', +'余粮' => '餘糧', +'余绪' => '餘緒', +'余缺' => '餘缺', +'余罪' => '餘罪', +'余羨' => '餘羨', +'余声' => '餘聲', +'余膏' => '餘膏', +'余兴' => '餘興', +'余蓄' => '餘蓄', +'余荫' => '餘蔭', +'余裕' => '餘裕', +'余角' => '餘角', +'余论' => '餘論', +'余责' => '餘責', +'余貾' => '餘貾', +'余辉' => '餘輝', +'余辜' => '餘辜', +'余酲' => '餘酲', +'余闰' => '餘閏', +'余闲' => '餘閒', +'余零' => '餘零', +'余震' => '餘震', +'余霞' => '餘霞', +'余音' => '餘音', +'余音绕梁' => '餘音繞梁', +'余韵' => '餘韻', +'余响' => '餘響', +'余额' => '餘額', +'余风' => '餘風', +'余食' => '餘食', +'余党' => '餘黨', +'余0' => '餘0', +'余1' => '餘1', +'余2' => '餘2', +'余3' => '餘3', +'余4' => '餘4', +'余5' => '餘5', +'余6' => '餘6', +'余7' => '餘7', +'余8' => '餘8', +'余9' => '餘9', +'馄饨面' => '餛飩麵', +'馆后一街' => '館後一街', +'馆后二街' => '館後二街', +'馆谷' => '館穀', +'喂乳' => '餵乳', +'喂了' => '餵了', +'喂奶' => '餵奶', +'喂给' => '餵給', +'喂羊' => '餵羊', +'喂猪' => '餵豬', +'喂过' => '餵過', +'喂鸡' => '餵雞', +'喂食' => '餵食', +'喂饱' => '餵飽', +'喂养' => '餵養', +'喂驴' => '餵驢', +'喂鱼' => '餵魚', +'喂鸭' => '餵鴨', +'喂鹅' => '餵鵝', +'饥寒' => '饑寒', +'饥民' => '饑民', +'饥渴' => '饑渴', +'饥溺' => '饑溺', +'饥饱' => '饑飽', +'饥馑' => '饑饉', +'首当其冲' => '首當其衝', +'香干' => '香乾', +'香山庄' => '香山庄', +'马干' => '馬乾', +'马后' => '馬後', +'马杆' => '馬杆', +'马表' => '馬錶', +'驻扎' => '駐紮', +'骀荡' => '駘蕩', +'腾冲' => '騰衝', +'惊赞' => '驚讚', +'骨子里' => '骨子裡', +'骨干' => '骨幹', +'骨灰坛' => '骨灰罈', +'骨坛' => '骨罈', +'骨头里挣出来的钱才做得肉' => '骨頭裡掙出來的錢纔做得肉', +'肮肮脏脏' => '骯骯髒髒', +'肮脏' => '骯髒', +'脏乱' => '髒亂', +'脏了' => '髒了', +'脏兮兮' => '髒兮兮', +'脏字' => '髒字', +'脏得' => '髒得', +'脏心' => '髒心', +'脏东西' => '髒東西', +'脏水' => '髒水', +'脏的' => '髒的', +'脏词' => '髒詞', +'脏话' => '髒話', +'脏钱' => '髒錢', +'体范' => '體範', +'高几' => '高几', +'高干扰' => '高干擾', +'高干预' => '高干預', +'高干' => '高幹', +'高度自制' => '高度自制', +'高于' => '高於', +'高升' => '高陞', +'髡发' => '髡髮', +'髭须' => '髭鬚', +'发上指冠' => '髮上指冠', +'发上冲冠' => '髮上沖冠', +'发乳' => '髮乳', +'发光可鉴' => '髮光可鑑', +'发匪' => '髮匪', +'发型' => '髮型', +'发夹' => '髮夾', +'发妻' => '髮妻', +'发姐' => '髮姐', +'发屋' => '髮屋', +'发带' => '髮帶', +'发廊' => '髮廊', +'发式' => '髮式', +'发引千钧' => '髮引千鈞', +'发指' => '髮指', +'发卷' => '髮捲', +'发根' => '髮根', +'发油' => '髮油', +'发漂' => '髮漂', +'发状' => '髮狀', +'发癣' => '髮癬', +'发短心长' => '髮短心長', +'发禁' => '髮禁', +'发笺' => '髮箋', +'发纱' => '髮紗', +'发结' => '髮結', +'发丝' => '髮絲', +'发网' => '髮網', +'发脚' => '髮腳', +'发肤' => '髮膚', +'发胶' => '髮膠', +'发菜' => '髮菜', +'发蜡' => '髮蠟', +'发踊冲冠' => '髮踊沖冠', +'发辫' => '髮辮', +'发针' => '髮針', +'发钗' => '髮釵', +'发长' => '髮長', +'发际' => '髮際', +'发雕' => '髮雕', +'发霜' => '髮霜', +'发饰' => '髮飾', +'发髻' => '髮髻', +'发鬓' => '髮鬢', +'髼松' => '髼鬆', +'鬅松' => '鬅鬆', +'松一口气' => '鬆一口氣', +'松了' => '鬆了', +'松些' => '鬆些', +'松劲' => '鬆勁', +'松动' => '鬆動', +'松口' => '鬆口', +'松土' => '鬆土', +'松宽' => '鬆寬', +'松弛' => '鬆弛', +'松快' => '鬆快', +'松懈' => '鬆懈', +'松手' => '鬆手', +'松掉' => '鬆掉', +'松散' => '鬆散', +'松柔' => '鬆柔', +'松气' => '鬆氣', +'松浮' => '鬆浮', +'松绑' => '鬆綁', +'松紧' => '鬆緊', +'松缓' => '鬆緩', +'松脆' => '鬆脆', +'松脱' => '鬆脫', +'松蛋' => '鬆蛋', +'松起' => '鬆起', +'松软' => '鬆軟', +'松通' => '鬆通', +'松开' => '鬆開', +'松饼' => '鬆餅', +'松松' => '鬆鬆', +'鬈发' => '鬈髮', +'胡子' => '鬍子', +'胡梢' => '鬍梢', +'胡渣' => '鬍渣', +'胡髭' => '鬍髭', +'胡须' => '鬍鬚', +'鬒发' => '鬒髮', +'须根' => '鬚根', +'须毛' => '鬚毛', +'须生' => '鬚生', +'须眉' => '鬚眉', +'须发' => '鬚髮', +'须须' => '鬚鬚', +'须鲨' => '鬚鯊', +'须鲸' => '鬚鯨', +'鬓发' => '鬢髮', +'斗上' => '鬥上', +'斗不过' => '鬥不過', +'斗了' => '鬥了', +'斗来斗去' => '鬥來鬥去', +'斗倒' => '鬥倒', +'斗分子' => '鬥分子', +'斗力' => '鬥力', +'斗劲' => '鬥勁', +'斗胜' => '鬥勝', +'斗口' => '鬥口', +'斗合' => '鬥合', +'斗嘴' => '鬥嘴', +'斗士' => '鬥士', +'斗富' => '鬥富', +'斗巧' => '鬥巧', +'斗幌子' => '鬥幌子', +'斗弄' => '鬥弄', +'斗引' => '鬥引', +'斗别气' => '鬥彆氣', +'斗彩' => '鬥彩', +'斗心眼' => '鬥心眼', +'斗志' => '鬥志', +'斗闷' => '鬥悶', +'斗成' => '鬥成', +'斗打' => '鬥打', +'斗批改' => '鬥批改', +'斗技' => '鬥技', +'斗文' => '鬥文', +'斗智' => '鬥智', +'斗暴' => '鬥暴', +'斗武' => '鬥武', +'斗殴' => '鬥毆', +'斗气' => '鬥氣', +'斗法' => '鬥法', +'斗争' => '鬥爭', +'斗争斗合' => '鬥爭鬥合', +'斗牌' => '鬥牌', +'斗牙拌齿' => '鬥牙拌齒', +'斗牙斗齿' => '鬥牙鬥齒', +'斗牛' => '鬥牛', +'斗犀台' => '鬥犀臺', +'斗犬' => '鬥犬', +'斗狠' => '鬥狠', +'斗叠' => '鬥疊', +'斗百草' => '鬥百草', +'斗眼' => '鬥眼', +'斗私批修' => '鬥私批修', +'斗而铸兵' => '鬥而鑄兵', +'斗而铸锥' => '鬥而鑄錐', +'斗脚' => '鬥腳', +'斗舰' => '鬥艦', +'斗茶' => '鬥茶', +'斗草' => '鬥草', +'斗叶儿' => '鬥葉兒', +'斗叶子' => '鬥葉子', +'斗着' => '鬥著', +'斗蟋蟀' => '鬥蟋蟀', +'斗话' => '鬥話', +'斗艳' => '鬥豔', +'斗起' => '鬥起', +'斗趣' => '鬥趣', +'斗闲气' => '鬥閑氣', +'斗鸡' => '鬥雞', +'斗雪红' => '鬥雪紅', +'斗头' => '鬥頭', +'斗风' => '鬥風', +'斗饤' => '鬥飣', +'斗斗' => '鬥鬥', +'斗哄' => '鬥鬨', +'斗鱼' => '鬥魚', +'斗鸭' => '鬥鴨', +'斗鹌鹑' => '鬥鵪鶉', +'斗丽' => '鬥麗', +'闹着玩儿' => '鬧著玩兒', +'闹钟' => '鬧鐘', +'哄动' => '鬨動', +'哄堂' => '鬨堂', +'哄笑' => '鬨笑', +'郁伊' => '鬱伊', +'郁勃' => '鬱勃', +'郁卒' => '鬱卒', +'郁堙不偶' => '鬱堙不偶', +'郁塞' => '鬱塞', +'郁垒' => '鬱壘', +'郁律' => '鬱律', +'郁悒' => '鬱悒', +'郁闷' => '鬱悶', +'郁愤' => '鬱憤', +'郁抑' => '鬱抑', +'郁挹' => '鬱挹', +'郁气' => '鬱氣', +'郁江' => '鬱江', +'郁沉沉' => '鬱沉沉', +'郁泱' => '鬱泱', +'郁火' => '鬱火', +'郁热' => '鬱熱', +'郁燠' => '鬱燠', +'郁症' => '鬱症', +'郁积' => '鬱積', +'郁纡' => '鬱紆', +'郁结' => '鬱結', +'郁蒸' => '鬱蒸', +'郁蓊' => '鬱蓊', +'郁血' => '鬱血', +'郁邑' => '鬱邑', +'郁郁' => '鬱郁', +'郁金' => '鬱金', +'郁闭' => '鬱閉', +'郁陶' => '鬱陶', +'郁郁不平' => '鬱鬱不平', +'郁郁不乐' => '鬱鬱不樂', +'郁郁寡欢' => '鬱鬱寡歡', +'郁郁而终' => '鬱鬱而終', +'郁郁葱葱' => '鬱鬱蔥蔥', +'郁黑' => '鬱黑', +'鬼谷子' => '鬼谷子', +'魂牵梦系' => '魂牽夢繫', +'魏征' => '魏徵', +'鱼干' => '魚乾', +'鱼松' => '魚鬆', +'鲸须' => '鯨鬚', +'鲇鱼' => '鯰魚', +'鸠占鹊巢' => '鳩佔鵲巢', +'凤梨干' => '鳳梨乾', +'鸣钟' => '鳴鐘', +'鸿案相庄' => '鴻案相莊', +'鸿范' => '鴻範', +'鸿篇巨制' => '鴻篇巨製', +'鹅准' => '鵝準', +'鹄发' => '鵠髮', +'雕心雁爪' => '鵰心雁爪', +'雕悍' => '鵰悍', +'雕翎' => '鵰翎', +'雕鹗' => '鵰鶚', +'鹤吊' => '鶴弔', +'鹤发' => '鶴髮', +'咸味' => '鹹味', +'咸嘴淡舌' => '鹹嘴淡舌', +'咸土' => '鹹土', +'咸度' => '鹹度', +'咸得' => '鹹得', +'咸批' => '鹹批', +'咸水' => '鹹水', +'咸派' => '鹹派', +'咸海' => '鹹海', +'咸淡' => '鹹淡', +'咸湖' => '鹹湖', +'咸汤' => '鹹湯', +'咸潟' => '鹹潟', +'咸的' => '鹹的', +'咸粥' => '鹹粥', +'咸肉' => '鹹肉', +'咸菜' => '鹹菜', +'咸菜干' => '鹹菜乾', +'咸蛋' => '鹹蛋', +'咸猪肉' => '鹹豬肉', +'咸类' => '鹹類', +'咸食' => '鹹食', +'咸鱼' => '鹹魚', +'咸鸭蛋' => '鹹鴨蛋', +'咸卤' => '鹹鹵', +'咸咸' => '鹹鹹', +'盐打怎么咸' => '鹽打怎麼鹹', +'盐卤' => '鹽滷', +'盐余' => '鹽餘', +'丽于' => '麗於', +'曲尘' => '麴塵', +'曲蘖' => '麴櫱', +'曲生' => '麴生', +'曲秀才' => '麴秀才', +'曲菌' => '麴菌', +'曲车' => '麴車', +'曲道士' => '麴道士', +'曲钱' => '麴錢', +'曲院' => '麴院', +'曲霉' => '麴黴', +'面人儿' => '麵人兒', +'面价' => '麵價', +'面包' => '麵包', +'面坊' => '麵坊', +'面坯儿' => '麵坯兒', +'面塑' => '麵塑', +'面店' => '麵店', +'面厂' => '麵廠', +'面摊' => '麵攤', +'面杖' => '麵杖', +'面条' => '麵條', +'面汤' => '麵湯', +'面浆' => '麵漿', +'面灰' => '麵灰', +'面疙瘩' => '麵疙瘩', +'面皮' => '麵皮', +'面码儿' => '麵碼兒', +'面筋' => '麵筋', +'面粉' => '麵粉', +'面糊' => '麵糊', +'面团' => '麵糰', +'面线' => '麵線', +'面缸' => '麵缸', +'面茶' => '麵茶', +'面食' => '麵食', +'面饺' => '麵餃', +'面饼' => '麵餅', +'面馆' => '麵館', +'麻药' => '麻藥', +'麻醉药' => '麻醉藥', +'麻酱面' => '麻醬麵', +'黄干黑瘦' => '黃乾黑瘦', +'黄历' => '黃曆', +'黄曲霉' => '黃曲霉', +'黄历史' => '黃歷史', +'黄金表' => '黃金表', +'黃鈺筑' => '黃鈺筑', +'黄钰筑' => '黃鈺筑', +'黄钟' => '黃鐘', +'黄发' => '黃髮', +'黄曲毒素' => '黃麴毒素', +'黑奴吁天录' => '黑奴籲天錄', +'黑发' => '黑髮', +'点钟' => '點鐘', +'霉毒' => '黴毒', +'霉素' => '黴素', +'霉菌' => '黴菌', +'霉黑' => '黴黑', +'霉黧' => '黴黧', +'鼓里' => '鼓裡', +'冬冬鼓' => '鼕鼕鼓', +'鼠药' => '鼠藥', +'鼠曲草' => '鼠麴草', +'鼻梁儿' => '鼻梁兒', +'鼻梁' => '鼻樑', +'鼻准' => '鼻準', +'齐王舍牛' => '齊王捨牛', +'齐庄' => '齊莊', +'齿危发秀' => '齒危髮秀', +'齿落发白' => '齒落髮白', +'齿发' => '齒髮', +'出儿' => '齣兒', +'出剧' => '齣劇', +'出动画' => '齣動畫', +'出卡通' => '齣卡通', +'出子' => '齣子', +'出戏' => '齣戲', +'出节目' => '齣節目', +'出电影' => '齣電影', +'出电视' => '齣電視', +'龙卷' => '龍捲', +'龙眼干' => '龍眼乾', +'龙须' => '龍鬚', +'龙斗虎伤' => '龍鬥虎傷', +'龟山庄' => '龜山庄', +'0多只' => '0多隻', +'0天后' => '0天後', +'0只' => '0隻', +'0余' => '0餘', +'1天后' => '1天後', +'1只' => '1隻', +'2天后' => '2天後', +'2只' => '2隻', +'3天后' => '3天後', +'3只' => '3隻', +'4天后' => '4天後', +'4只' => '4隻', +'5天后' => '5天後', +'5只' => '5隻', +'6天后' => '6天後', +'6只' => '6隻', +'7天后' => '7天後', +'7只' => '7隻', +'8天后' => '8天後', +'8只' => '8隻', +'9天后' => '9天後', +'9只' => '9隻', ); $zh2Hans = array( -"餘"=>"余", -"瀋"=>"沈", -"畫"=>"划", -"鍾"=>"钟", -"靦"=>"腼", -"鯰"=>"鲇", -"鹼"=>"硷", -"㠏"=>"㟆", -"𡞵"=>"㛟", -"万"=>"万", -"与"=>"与", -"丟"=>"丢", -"並"=>"并", -"丰"=>"丰", -"乾"=>"干", -"亂"=>"乱", -"云"=>"云", -"亙"=>"亘", -"亞"=>"亚", -"价"=>"价", -"佇"=>"伫", -"佈"=>"布", -"体"=>"体", -"佔"=>"占", -"併"=>"并", -"來"=>"来", -"侖"=>"仑", -"侶"=>"侣", -"俁"=>"俣", -"係"=>"系", -"俔"=>"伣", -"俠"=>"侠", -"倀"=>"伥", -"倆"=>"俩", -"倈"=>"俫", -"倉"=>"仓", -"個"=>"个", -"們"=>"们", -"倖"=>"幸", -"倫"=>"伦", -"偉"=>"伟", -"側"=>"侧", -"偵"=>"侦", -"偽"=>"伪", -"傑"=>"杰", -"傖"=>"伧", -"傘"=>"伞", -"備"=>"备", -"傢"=>"家", -"傭"=>"佣", -"傯"=>"偬", -"傳"=>"传", -"傴"=>"伛", -"債"=>"债", -"傷"=>"伤", -"傾"=>"倾", -"僂"=>"偻", -"僅"=>"仅", -"僉"=>"佥", -"僑"=>"侨", -"僕"=>"仆", -"僞"=>"伪", -"僥"=>"侥", -"僨"=>"偾", -"僱"=>"雇", -"價"=>"价", -"儀"=>"仪", -"儂"=>"侬", -"億"=>"亿", -"儈"=>"侩", -"儉"=>"俭", -"儐"=>"傧", -"儔"=>"俦", -"儕"=>"侪", -"儘"=>"尽", -"償"=>"偿", -"優"=>"优", -"儲"=>"储", -"儷"=>"俪", -"儸"=>"㑩", -"儺"=>"傩", -"儻"=>"傥", -"儼"=>"俨", -"儿"=>"儿", -"兇"=>"凶", -"兌"=>"兑", -"兒"=>"儿", -"兗"=>"兖", -"党"=>"党", -"內"=>"内", -"兩"=>"两", -"冊"=>"册", -"冪"=>"幂", -"凈"=>"净", -"凍"=>"冻", -"凜"=>"凛", -"几"=>"几", -"凱"=>"凯", -"別"=>"别", -"刪"=>"删", -"剄"=>"刭", -"則"=>"则", -"剋"=>"克", -"剎"=>"刹", -"剗"=>"刬", -"剛"=>"刚", -"剝"=>"剥", -"剮"=>"剐", -"剴"=>"剀", -"創"=>"创", -"劃"=>"划", -"劇"=>"剧", -"劉"=>"刘", -"劊"=>"刽", -"劌"=>"刿", -"劍"=>"剑", -"劏"=>"㓥", -"劑"=>"剂", -"劚"=>"㔉", -"勁"=>"劲", -"動"=>"动", -"務"=>"务", -"勛"=>"勋", -"勝"=>"胜", -"勞"=>"劳", -"勢"=>"势", -"勩"=>"勚", -"勱"=>"劢", -"勵"=>"励", -"勸"=>"劝", -"勻"=>"匀", -"匭"=>"匦", -"匯"=>"汇", -"匱"=>"匮", -"區"=>"区", -"協"=>"协", -"卻"=>"却", -"厂"=>"厂", -"厙"=>"厍", -"厠"=>"厕", -"厤"=>"历", -"厭"=>"厌", -"厲"=>"厉", -"厴"=>"厣", -"參"=>"参", -"叄"=>"叁", -"叢"=>"丛", -"台"=>"台", -"叶"=>"叶", -"后"=>"后", -"吒"=>"咤", -"吳"=>"吴", -"吶"=>"呐", -"呂"=>"吕", -"咼"=>"呙", -"員"=>"员", -"唄"=>"呗", -"唚"=>"吣", -"問"=>"问", -"啓"=>"启", -"啞"=>"哑", -"啟"=>"启", -"啢"=>"唡", -"喎"=>"㖞", -"喚"=>"唤", -"喪"=>"丧", -"喬"=>"乔", -"單"=>"单", -"喲"=>"哟", -"嗆"=>"呛", -"嗇"=>"啬", -"嗊"=>"唝", -"嗎"=>"吗", -"嗚"=>"呜", -"嗩"=>"唢", -"嗶"=>"哔", -"嘆"=>"叹", -"嘍"=>"喽", -"嘔"=>"呕", -"嘖"=>"啧", -"嘗"=>"尝", -"嘜"=>"唛", -"嘩"=>"哗", -"嘮"=>"唠", -"嘯"=>"啸", -"嘰"=>"叽", -"嘵"=>"哓", -"嘸"=>"呒", -"嘽"=>"啴", -"噁"=>"恶", -"噓"=>"嘘", -"噚"=>"㖊", -"噝"=>"咝", -"噠"=>"哒", -"噥"=>"哝", -"噦"=>"哕", -"噯"=>"嗳", -"噲"=>"哙", -"噴"=>"喷", -"噸"=>"吨", -"噹"=>"当", -"嚀"=>"咛", -"嚇"=>"吓", -"嚌"=>"哜", -"嚕"=>"噜", -"嚙"=>"啮", -"嚥"=>"咽", -"嚦"=>"呖", -"嚨"=>"咙", -"嚮"=>"向", -"嚲"=>"亸", -"嚳"=>"喾", -"嚴"=>"严", -"嚶"=>"嘤", -"囀"=>"啭", -"囁"=>"嗫", -"囂"=>"嚣", -"囅"=>"冁", -"囈"=>"呓", -"囌"=>"苏", -"囑"=>"嘱", -"囪"=>"囱", -"圇"=>"囵", -"國"=>"国", -"圍"=>"围", -"園"=>"园", -"圓"=>"圆", -"圖"=>"图", -"團"=>"团", -"坏"=>"坏", -"垵"=>"埯", -"埡"=>"垭", -"埰"=>"采", -"執"=>"执", -"堅"=>"坚", -"堊"=>"垩", -"堖"=>"垴", -"堝"=>"埚", -"堯"=>"尧", -"報"=>"报", -"場"=>"场", -"塊"=>"块", -"塋"=>"茔", -"塏"=>"垲", -"塒"=>"埘", -"塗"=>"涂", -"塚"=>"冢", -"塢"=>"坞", -"塤"=>"埙", -"塵"=>"尘", -"塹"=>"堑", -"墊"=>"垫", -"墜"=>"坠", -"墮"=>"堕", -"墳"=>"坟", -"墻"=>"墙", -"墾"=>"垦", -"壇"=>"坛", -"壈"=>"𡒄", -"壋"=>"垱", -"壓"=>"压", -"壘"=>"垒", -"壙"=>"圹", -"壚"=>"垆", -"壞"=>"坏", -"壟"=>"垄", -"壠"=>"垅", -"壢"=>"坜", -"壩"=>"坝", -"壯"=>"壮", -"壺"=>"壶", -"壼"=>"壸", -"壽"=>"寿", -"夠"=>"够", -"夢"=>"梦", -"夥"=>"伙", -"夾"=>"夹", -"奐"=>"奂", -"奧"=>"奥", -"奩"=>"奁", -"奪"=>"夺", -"奬"=>"奖", -"奮"=>"奋", -"奼"=>"姹", -"妝"=>"妆", -"姍"=>"姗", -"姦"=>"奸", -"娛"=>"娱", -"婁"=>"娄", -"婦"=>"妇", -"婭"=>"娅", -"媧"=>"娲", -"媯"=>"妫", -"媼"=>"媪", -"媽"=>"妈", -"嫗"=>"妪", -"嫵"=>"妩", -"嫻"=>"娴", -"嫿"=>"婳", -"嬀"=>"妫", -"嬈"=>"娆", -"嬋"=>"婵", -"嬌"=>"娇", -"嬙"=>"嫱", -"嬡"=>"嫒", -"嬤"=>"嬷", -"嬪"=>"嫔", -"嬰"=>"婴", -"嬸"=>"婶", -"孌"=>"娈", -"孫"=>"孙", -"學"=>"学", -"孿"=>"孪", -"宁"=>"宁", -"宮"=>"宫", -"寀"=>"采", -"寢"=>"寝", -"實"=>"实", -"寧"=>"宁", -"審"=>"审", -"寫"=>"写", -"寬"=>"宽", -"寵"=>"宠", -"寶"=>"宝", -"將"=>"将", -"專"=>"专", -"尋"=>"寻", -"對"=>"对", -"導"=>"导", -"尷"=>"尴", -"屆"=>"届", -"屍"=>"尸", -"屓"=>"屃", -"屜"=>"屉", -"屢"=>"屡", -"層"=>"层", -"屨"=>"屦", -"屬"=>"属", -"岡"=>"冈", -"峴"=>"岘", -"島"=>"岛", -"峽"=>"峡", -"崍"=>"崃", -"崑"=>"昆", -"崗"=>"岗", -"崙"=>"仑", -"崢"=>"峥", -"崬"=>"岽", -"嵐"=>"岚", -"嶁"=>"嵝", -"嶄"=>"崭", -"嶇"=>"岖", -"嶔"=>"嵚", -"嶗"=>"崂", -"嶠"=>"峤", -"嶢"=>"峣", -"嶧"=>"峄", -"嶮"=>"崄", -"嶴"=>"岙", -"嶸"=>"嵘", -"嶺"=>"岭", -"嶼"=>"屿", -"嶽"=>"岳", -"巋"=>"岿", -"巒"=>"峦", -"巔"=>"巅", -"巰"=>"巯", -"帘"=>"帘", -"帥"=>"帅", -"師"=>"师", -"帳"=>"帐", -"帶"=>"带", -"幀"=>"帧", -"幃"=>"帏", -"幗"=>"帼", -"幘"=>"帻", -"幟"=>"帜", -"幣"=>"币", -"幫"=>"帮", -"幬"=>"帱", -"幹"=>"干", -"幺"=>"么", -"幾"=>"几", -"广"=>"广", -"庫"=>"库", -"廁"=>"厕", -"廂"=>"厢", -"廄"=>"厩", -"廈"=>"厦", -"廚"=>"厨", -"廝"=>"厮", -"廟"=>"庙", -"廠"=>"厂", -"廡"=>"庑", -"廢"=>"废", -"廣"=>"广", -"廩"=>"廪", -"廬"=>"庐", -"廳"=>"厅", -"弒"=>"弑", -"弔"=>"吊", -"弳"=>"弪", -"張"=>"张", -"強"=>"强", -"彆"=>"别", -"彈"=>"弹", -"彌"=>"弥", -"彎"=>"弯", -"彙"=>"汇", -"彞"=>"彝", -"彥"=>"彦", -"後"=>"后", -"徑"=>"径", -"從"=>"从", -"徠"=>"徕", -"復"=>"复", -"徵"=>"征", -"徹"=>"彻", -"志"=>"志", -"恆"=>"恒", -"恥"=>"耻", -"悅"=>"悦", -"悞"=>"悮", -"悵"=>"怅", -"悶"=>"闷", -"惡"=>"恶", -"惱"=>"恼", -"惲"=>"恽", -"惻"=>"恻", -"愛"=>"爱", -"愜"=>"惬", -"愨"=>"悫", -"愴"=>"怆", -"愷"=>"恺", -"愾"=>"忾", -"愿"=>"愿", -"慄"=>"栗", -"態"=>"态", -"慍"=>"愠", -"慘"=>"惨", -"慚"=>"惭", -"慟"=>"恸", -"慣"=>"惯", -"慤"=>"悫", -"慪"=>"怄", -"慫"=>"怂", -"慮"=>"虑", -"慳"=>"悭", -"慶"=>"庆", -"慾"=>"欲", -"憂"=>"忧", -"憊"=>"惫", -"憐"=>"怜", -"憑"=>"凭", -"憒"=>"愦", -"憚"=>"惮", -"憤"=>"愤", -"憫"=>"悯", -"憮"=>"怃", -"憲"=>"宪", -"憶"=>"忆", -"懇"=>"恳", -"應"=>"应", -"懌"=>"怿", -"懍"=>"懔", -"懞"=>"蒙", -"懟"=>"怼", -"懣"=>"懑", -"懨"=>"恹", -"懲"=>"惩", -"懶"=>"懒", -"懷"=>"怀", -"懸"=>"悬", -"懺"=>"忏", -"懼"=>"惧", -"懾"=>"慑", -"戀"=>"恋", -"戇"=>"戆", -"戔"=>"戋", -"戧"=>"戗", -"戩"=>"戬", -"戰"=>"战", -"戱"=>"戯", -"戲"=>"戏", -"戶"=>"户", -"担"=>"担", -"拋"=>"抛", -"拚"=>"拼", -"挩"=>"捝", -"挾"=>"挟", -"捨"=>"舍", -"捫"=>"扪", -"据"=>"据", -"捲"=>"卷", -"掃"=>"扫", -"掄"=>"抡", -"掗"=>"挜", -"掙"=>"挣", -"掛"=>"挂", -"採"=>"采", -"揀"=>"拣", -"揚"=>"扬", -"換"=>"换", -"揮"=>"挥", -"損"=>"损", -"搖"=>"摇", -"搗"=>"捣", -"搵"=>"揾", -"搶"=>"抢", -"摑"=>"掴", -"摜"=>"掼", -"摟"=>"搂", -"摯"=>"挚", -"摳"=>"抠", -"摶"=>"抟", -"摺"=>"折", -"摻"=>"掺", -"撈"=>"捞", -"撏"=>"挦", -"撐"=>"撑", -"撓"=>"挠", -"撝"=>"㧑", -"撟"=>"挢", -"撣"=>"掸", -"撥"=>"拨", -"撫"=>"抚", -"撲"=>"扑", -"撳"=>"揿", -"撻"=>"挞", -"撾"=>"挝", -"撿"=>"捡", -"擁"=>"拥", -"擄"=>"掳", -"擇"=>"择", -"擊"=>"击", -"擋"=>"挡", -"擓"=>"㧟", -"擔"=>"担", -"據"=>"据", -"擠"=>"挤", -"擬"=>"拟", -"擯"=>"摈", -"擰"=>"拧", -"擱"=>"搁", -"擲"=>"掷", -"擴"=>"扩", -"擷"=>"撷", -"擺"=>"摆", -"擻"=>"擞", -"擼"=>"撸", -"擾"=>"扰", -"攄"=>"摅", -"攆"=>"撵", -"攏"=>"拢", -"攔"=>"拦", -"攖"=>"撄", -"攙"=>"搀", -"攛"=>"撺", -"攜"=>"携", -"攝"=>"摄", -"攢"=>"攒", -"攣"=>"挛", -"攤"=>"摊", -"攪"=>"搅", -"攬"=>"揽", -"敗"=>"败", -"敘"=>"叙", -"敵"=>"敌", -"數"=>"数", -"斂"=>"敛", -"斃"=>"毙", -"斕"=>"斓", -"斬"=>"斩", -"斷"=>"断", -"於"=>"于", -"昇"=>"升", -"時"=>"时", -"晉"=>"晋", -"晝"=>"昼", -"暈"=>"晕", -"暉"=>"晖", -"暘"=>"旸", -"暢"=>"畅", -"暫"=>"暂", -"曄"=>"晔", -"曆"=>"历", -"曇"=>"昙", -"曉"=>"晓", -"曏"=>"向", -"曖"=>"暧", -"曠"=>"旷", -"曨"=>"昽", -"曬"=>"晒", -"書"=>"书", -"會"=>"会", -"朧"=>"胧", -"朮"=>"术", -"東"=>"东", -"杴"=>"锨", -"极"=>"极", -"柜"=>"柜", -"柵"=>"栅", -"桿"=>"杆", -"梔"=>"栀", -"梘"=>"枧", -"條"=>"条", -"梟"=>"枭", -"梲"=>"棁", -"棄"=>"弃", -"棖"=>"枨", -"棗"=>"枣", -"棟"=>"栋", -"棧"=>"栈", -"棲"=>"栖", -"棶"=>"梾", -"椏"=>"桠", -"楊"=>"杨", -"楓"=>"枫", -"楨"=>"桢", -"業"=>"业", -"極"=>"极", -"榦"=>"干", -"榪"=>"杩", -"榮"=>"荣", -"榲"=>"榅", -"榿"=>"桤", -"構"=>"构", -"槍"=>"枪", -"槤"=>"梿", -"槧"=>"椠", -"槨"=>"椁", -"槳"=>"桨", -"樁"=>"桩", -"樂"=>"乐", -"樅"=>"枞", -"樑"=>"梁", -"樓"=>"楼", -"標"=>"标", -"樞"=>"枢", -"樣"=>"样", -"樸"=>"朴", -"樹"=>"树", -"樺"=>"桦", -"橈"=>"桡", -"橋"=>"桥", -"機"=>"机", -"橢"=>"椭", -"橫"=>"横", -"檁"=>"檩", -"檉"=>"柽", -"檔"=>"档", -"檜"=>"桧", -"檟"=>"槚", -"檢"=>"检", -"檣"=>"樯", -"檮"=>"梼", -"檯"=>"台", -"檳"=>"槟", -"檸"=>"柠", -"檻"=>"槛", -"櫃"=>"柜", -"櫓"=>"橹", -"櫚"=>"榈", -"櫛"=>"栉", -"櫝"=>"椟", -"櫞"=>"橼", -"櫟"=>"栎", -"櫥"=>"橱", -"櫧"=>"槠", -"櫨"=>"栌", -"櫪"=>"枥", -"櫫"=>"橥", -"櫬"=>"榇", -"櫱"=>"蘖", -"櫳"=>"栊", -"櫸"=>"榉", -"櫻"=>"樱", -"欄"=>"栏", -"欅"=>"榉", -"權"=>"权", -"欏"=>"椤", -"欒"=>"栾", -"欖"=>"榄", -"欞"=>"棂", -"欽"=>"钦", -"歐"=>"欧", -"歟"=>"欤", -"歡"=>"欢", -"歲"=>"岁", -"歷"=>"历", -"歸"=>"归", -"歿"=>"殁", -"殘"=>"残", -"殞"=>"殒", -"殤"=>"殇", -"殨"=>"㱮", -"殫"=>"殚", -"殮"=>"殓", -"殯"=>"殡", -"殰"=>"㱩", -"殲"=>"歼", -"殺"=>"杀", -"殻"=>"壳", -"殼"=>"壳", -"毀"=>"毁", -"毆"=>"殴", -"毿"=>"毵", -"氂"=>"牦", -"氈"=>"毡", -"氌"=>"氇", -"氣"=>"气", -"氫"=>"氢", -"氬"=>"氩", -"氳"=>"氲", -"汙"=>"污", -"決"=>"决", -"沒"=>"没", -"沖"=>"冲", -"況"=>"况", -"洶"=>"汹", -"浹"=>"浃", -"涂"=>"涂", -"涇"=>"泾", -"涼"=>"凉", -"淒"=>"凄", -"淚"=>"泪", -"淥"=>"渌", -"淨"=>"净", -"淩"=>"凌", -"淪"=>"沦", -"淵"=>"渊", -"淶"=>"涞", -"淺"=>"浅", -"渙"=>"涣", -"減"=>"减", -"渦"=>"涡", -"測"=>"测", -"渾"=>"浑", -"湊"=>"凑", -"湞"=>"浈", -"湧"=>"涌", -"湯"=>"汤", -"溈"=>"沩", -"準"=>"准", -"溝"=>"沟", -"溫"=>"温", -"滄"=>"沧", -"滅"=>"灭", -"滌"=>"涤", -"滎"=>"荥", -"滙"=>"汇", -"滬"=>"沪", -"滯"=>"滞", -"滲"=>"渗", -"滷"=>"卤", -"滸"=>"浒", -"滻"=>"浐", -"滾"=>"滚", -"滿"=>"满", -"漁"=>"渔", -"漚"=>"沤", -"漢"=>"汉", -"漣"=>"涟", -"漬"=>"渍", -"漲"=>"涨", -"漵"=>"溆", -"漸"=>"渐", -"漿"=>"浆", -"潁"=>"颍", -"潑"=>"泼", -"潔"=>"洁", -"潙"=>"沩", -"潛"=>"潜", -"潤"=>"润", -"潯"=>"浔", -"潰"=>"溃", -"潷"=>"滗", -"潿"=>"涠", -"澀"=>"涩", -"澆"=>"浇", -"澇"=>"涝", -"澐"=>"沄", -"澗"=>"涧", -"澠"=>"渑", -"澤"=>"泽", -"澦"=>"滪", -"澩"=>"泶", -"澮"=>"浍", -"澱"=>"淀", -"濁"=>"浊", -"濃"=>"浓", -"濕"=>"湿", -"濘"=>"泞", -"濛"=>"蒙", -"濟"=>"济", -"濤"=>"涛", -"濫"=>"滥", -"濰"=>"潍", -"濱"=>"滨", -"濺"=>"溅", -"濼"=>"泺", -"濾"=>"滤", -"瀅"=>"滢", -"瀆"=>"渎", -"瀇"=>"㲿", -"瀉"=>"泻", -"瀋"=>"沈", -"瀏"=>"浏", -"瀕"=>"濒", -"瀘"=>"泸", -"瀝"=>"沥", -"瀟"=>"潇", -"瀠"=>"潆", -"瀦"=>"潴", -"瀧"=>"泷", -"瀨"=>"濑", -"瀰"=>"弥", -"瀲"=>"潋", -"瀾"=>"澜", -"灃"=>"沣", -"灄"=>"滠", -"灑"=>"洒", -"灕"=>"漓", -"灘"=>"滩", -"灝"=>"灏", -"灠"=>"漤", -"灣"=>"湾", -"灤"=>"滦", -"灧"=>"滟", -"災"=>"灾", -"為"=>"为", -"烏"=>"乌", -"烴"=>"烃", -"無"=>"无", -"煉"=>"炼", -"煒"=>"炜", -"煙"=>"烟", -"煢"=>"茕", -"煥"=>"焕", -"煩"=>"烦", -"煬"=>"炀", -"煱"=>"㶽", -"熅"=>"煴", -"熒"=>"荧", -"熗"=>"炝", -"熱"=>"热", -"熲"=>"颎", -"熾"=>"炽", -"燁"=>"烨", -"燈"=>"灯", -"燉"=>"炖", -"燒"=>"烧", -"燙"=>"烫", -"燜"=>"焖", -"營"=>"营", -"燦"=>"灿", -"燬"=>"毁", -"燭"=>"烛", -"燴"=>"烩", -"燶"=>"㶶", -"燼"=>"烬", -"燾"=>"焘", -"爍"=>"烁", -"爐"=>"炉", -"爛"=>"烂", -"爭"=>"争", -"爲"=>"为", -"爺"=>"爷", -"爾"=>"尔", -"牆"=>"墙", -"牘"=>"牍", -"牽"=>"牵", -"犖"=>"荦", -"犢"=>"犊", -"犧"=>"牺", -"狀"=>"状", -"狹"=>"狭", -"狽"=>"狈", -"猙"=>"狰", -"猶"=>"犹", -"猻"=>"狲", -"獁"=>"犸", -"獃"=>"呆", -"獄"=>"狱", -"獅"=>"狮", -"獎"=>"奖", -"獨"=>"独", -"獪"=>"狯", -"獫"=>"猃", -"獮"=>"狝", -"獰"=>"狞", -"獱"=>"㺍", -"獲"=>"获", -"獵"=>"猎", -"獷"=>"犷", -"獸"=>"兽", -"獺"=>"獭", -"獻"=>"献", -"獼"=>"猕", -"玀"=>"猡", -"現"=>"现", -"琺"=>"珐", -"琿"=>"珲", -"瑋"=>"玮", -"瑒"=>"玚", -"瑣"=>"琐", -"瑤"=>"瑶", -"瑩"=>"莹", -"瑪"=>"玛", -"瑲"=>"玱", -"璉"=>"琏", -"璣"=>"玑", -"璦"=>"瑷", -"璫"=>"珰", -"環"=>"环", -"璽"=>"玺", -"瓊"=>"琼", -"瓏"=>"珑", -"瓔"=>"璎", -"瓚"=>"瓒", -"甌"=>"瓯", -"產"=>"产", -"産"=>"产", -"甦"=>"苏", -"畝"=>"亩", -"畢"=>"毕", -"異"=>"异", -"畵"=>"画", -"當"=>"当", -"疇"=>"畴", -"疊"=>"叠", -"痙"=>"痉", -"痾"=>"疴", -"瘂"=>"痖", -"瘋"=>"疯", -"瘍"=>"疡", -"瘓"=>"痪", -"瘞"=>"瘗", -"瘡"=>"疮", -"瘧"=>"疟", -"瘮"=>"瘆", -"瘲"=>"疭", -"瘺"=>"瘘", -"瘻"=>"瘘", -"療"=>"疗", -"癆"=>"痨", -"癇"=>"痫", -"癉"=>"瘅", -"癘"=>"疠", -"癟"=>"瘪", -"癢"=>"痒", -"癤"=>"疖", -"癥"=>"症", -"癧"=>"疬", -"癩"=>"癞", -"癬"=>"癣", -"癭"=>"瘿", -"癮"=>"瘾", -"癰"=>"痈", -"癱"=>"瘫", -"癲"=>"癫", -"發"=>"发", -"皚"=>"皑", -"皰"=>"疱", -"皸"=>"皲", -"皺"=>"皱", -"盃"=>"杯", -"盜"=>"盗", -"盞"=>"盏", -"盡"=>"尽", -"監"=>"监", -"盤"=>"盘", -"盧"=>"卢", -"盪"=>"荡", -"眥"=>"眦", -"眾"=>"众", -"睏"=>"困", -"睜"=>"睁", -"睞"=>"睐", -"瞘"=>"眍", -"瞜"=>"䁖", -"瞞"=>"瞒", -"瞭"=>"了", -"瞶"=>"瞆", -"瞼"=>"睑", -"矇"=>"蒙", -"矓"=>"眬", -"矚"=>"瞩", -"矯"=>"矫", -"硃"=>"朱", -"硜"=>"硁", -"硤"=>"硖", -"硨"=>"砗", -"确"=>"确", -"硯"=>"砚", -"碩"=>"硕", -"碭"=>"砀", -"碸"=>"砜", -"確"=>"确", -"碼"=>"码", -"磑"=>"硙", -"磚"=>"砖", -"磣"=>"碜", -"磧"=>"碛", -"磯"=>"矶", -"磽"=>"硗", -"礆"=>"硷", -"礎"=>"础", -"礙"=>"碍", -"礦"=>"矿", -"礪"=>"砺", -"礫"=>"砾", -"礬"=>"矾", -"礱"=>"砻", -"祿"=>"禄", -"禍"=>"祸", -"禎"=>"祯", -"禕"=>"祎", -"禡"=>"祃", -"禦"=>"御", -"禪"=>"禅", -"禮"=>"礼", -"禰"=>"祢", -"禱"=>"祷", -"禿"=>"秃", -"秈"=>"籼", -"种"=>"种", -"稅"=>"税", -"稈"=>"秆", -"稏"=>"䅉", -"稜"=>"棱", -"稟"=>"禀", -"種"=>"种", -"稱"=>"称", -"穀"=>"谷", -"穌"=>"稣", -"積"=>"积", -"穎"=>"颖", -"穠"=>"秾", -"穡"=>"穑", -"穢"=>"秽", -"穩"=>"稳", -"穫"=>"获", -"穭"=>"稆", -"窩"=>"窝", -"窪"=>"洼", -"窮"=>"穷", -"窯"=>"窑", -"窵"=>"窎", -"窶"=>"窭", -"窺"=>"窥", -"竄"=>"窜", -"竅"=>"窍", -"竇"=>"窦", -"竈"=>"灶", -"竊"=>"窃", -"竪"=>"竖", -"競"=>"竞", -"筆"=>"笔", -"筍"=>"笋", -"筑"=>"筑", -"筧"=>"笕", -"筴"=>"䇲", -"箋"=>"笺", -"箏"=>"筝", -"節"=>"节", -"範"=>"范", -"築"=>"筑", -"篋"=>"箧", -"篔"=>"筼", -"篤"=>"笃", -"篩"=>"筛", -"篳"=>"筚", -"簀"=>"箦", -"簍"=>"篓", -"簞"=>"箪", -"簡"=>"简", -"簣"=>"篑", -"簫"=>"箫", -"簹"=>"筜", -"簽"=>"签", -"簾"=>"帘", -"籃"=>"篮", -"籌"=>"筹", -"籙"=>"箓", -"籜"=>"箨", -"籟"=>"籁", -"籠"=>"笼", -"籤"=>"签", -"籩"=>"笾", -"籪"=>"簖", -"籬"=>"篱", -"籮"=>"箩", -"籲"=>"吁", -"粵"=>"粤", -"糝"=>"糁", -"糞"=>"粪", -"糧"=>"粮", -"糰"=>"团", -"糲"=>"粝", -"糴"=>"籴", -"糶"=>"粜", -"糹"=>"纟", -"糾"=>"纠", -"紀"=>"纪", -"紂"=>"纣", -"約"=>"约", -"紅"=>"红", -"紆"=>"纡", -"紇"=>"纥", -"紈"=>"纨", -"紉"=>"纫", -"紋"=>"纹", -"納"=>"纳", -"紐"=>"纽", -"紓"=>"纾", -"純"=>"纯", -"紕"=>"纰", -"紖"=>"纼", -"紗"=>"纱", -"紘"=>"纮", -"紙"=>"纸", -"級"=>"级", -"紛"=>"纷", -"紜"=>"纭", -"紝"=>"纴", -"紡"=>"纺", -"紬"=>"䌷", -"紮"=>"扎", -"細"=>"细", -"紱"=>"绂", -"紲"=>"绁", -"紳"=>"绅", -"紵"=>"纻", -"紹"=>"绍", -"紺"=>"绀", -"紼"=>"绋", -"紿"=>"绐", -"絀"=>"绌", -"終"=>"终", -"組"=>"组", -"絅"=>"䌹", -"絆"=>"绊", -"絎"=>"绗", -"結"=>"结", -"絕"=>"绝", -"絛"=>"绦", -"絝"=>"绔", -"絞"=>"绞", -"絡"=>"络", -"絢"=>"绚", -"給"=>"给", -"絨"=>"绒", -"絰"=>"绖", -"統"=>"统", -"絲"=>"丝", -"絳"=>"绛", -"絶"=>"绝", -"絹"=>"绢", -"綁"=>"绑", -"綃"=>"绡", -"綆"=>"绠", -"綈"=>"绨", -"綉"=>"绣", -"綌"=>"绤", -"綏"=>"绥", -"綐"=>"䌼", -"經"=>"经", -"綜"=>"综", -"綞"=>"缍", -"綠"=>"绿", -"綢"=>"绸", -"綣"=>"绻", -"綫"=>"线", -"綬"=>"绶", -"維"=>"维", -"綯"=>"绹", -"綰"=>"绾", -"綱"=>"纲", -"網"=>"网", -"綳"=>"绷", -"綴"=>"缀", -"綵"=>"䌽", -"綸"=>"纶", -"綹"=>"绺", -"綺"=>"绮", -"綻"=>"绽", -"綽"=>"绰", -"綾"=>"绫", -"綿"=>"绵", -"緄"=>"绲", -"緇"=>"缁", -"緊"=>"紧", -"緋"=>"绯", -"緑"=>"绿", -"緒"=>"绪", -"緓"=>"绬", -"緔"=>"绱", -"緗"=>"缃", -"緘"=>"缄", -"緙"=>"缂", -"線"=>"线", -"緝"=>"缉", -"緞"=>"缎", -"締"=>"缔", -"緡"=>"缗", -"緣"=>"缘", -"緦"=>"缌", -"編"=>"编", -"緩"=>"缓", -"緬"=>"缅", -"緯"=>"纬", -"緱"=>"缑", -"緲"=>"缈", -"練"=>"练", -"緶"=>"缏", -"緹"=>"缇", -"緻"=>"致", -"縈"=>"萦", -"縉"=>"缙", -"縊"=>"缢", -"縋"=>"缒", -"縐"=>"绉", -"縑"=>"缣", -"縕"=>"缊", -"縗"=>"缞", -"縛"=>"缚", -"縝"=>"缜", -"縞"=>"缟", -"縟"=>"缛", -"縣"=>"县", -"縧"=>"绦", -"縫"=>"缝", -"縭"=>"缡", -"縮"=>"缩", -"縱"=>"纵", -"縲"=>"缧", -"縳"=>"䌸", -"縴"=>"纤", -"縵"=>"缦", -"縶"=>"絷", -"縷"=>"缕", -"縹"=>"缥", -"總"=>"总", -"績"=>"绩", -"繃"=>"绷", -"繅"=>"缫", -"繆"=>"缪", -"繒"=>"缯", -"織"=>"织", -"繕"=>"缮", -"繚"=>"缭", -"繞"=>"绕", -"繡"=>"绣", -"繢"=>"缋", -"繩"=>"绳", -"繪"=>"绘", -"繫"=>"系", -"繭"=>"茧", -"繮"=>"缰", -"繯"=>"缳", -"繰"=>"缲", -"繳"=>"缴", -"繸"=>"䍁", -"繹"=>"绎", -"繼"=>"继", -"繽"=>"缤", -"繾"=>"缱", -"繿"=>"䍀", -"纈"=>"缬", -"纊"=>"纩", -"續"=>"续", -"纍"=>"累", -"纏"=>"缠", -"纓"=>"缨", -"纔"=>"才", -"纖"=>"纤", -"纘"=>"缵", -"纜"=>"缆", -"缽"=>"钵", -"罈"=>"坛", -"罌"=>"罂", -"罰"=>"罚", -"罵"=>"骂", -"罷"=>"罢", -"羅"=>"罗", -"羆"=>"罴", -"羈"=>"羁", -"羋"=>"芈", -"羥"=>"羟", -"義"=>"义", -"習"=>"习", -"翹"=>"翘", -"耬"=>"耧", -"耮"=>"耢", -"聖"=>"圣", -"聞"=>"闻", -"聯"=>"联", -"聰"=>"聪", -"聲"=>"声", -"聳"=>"耸", -"聵"=>"聩", -"聶"=>"聂", -"職"=>"职", -"聹"=>"聍", -"聽"=>"听", -"聾"=>"聋", -"肅"=>"肃", -"胜"=>"胜", -"脅"=>"胁", -"脈"=>"脉", -"脛"=>"胫", -"脫"=>"脱", -"脹"=>"胀", -"腊"=>"腊", -"腎"=>"肾", -"腖"=>"胨", -"腡"=>"脶", -"腦"=>"脑", -"腫"=>"肿", -"腳"=>"脚", -"腸"=>"肠", -"膃"=>"腽", -"膚"=>"肤", -"膠"=>"胶", -"膩"=>"腻", -"膽"=>"胆", -"膾"=>"脍", -"膿"=>"脓", -"臉"=>"脸", -"臍"=>"脐", -"臏"=>"膑", -"臘"=>"腊", -"臚"=>"胪", -"臟"=>"脏", -"臠"=>"脔", -"臢"=>"臜", -"臥"=>"卧", -"臨"=>"临", -"臺"=>"台", -"與"=>"与", -"興"=>"兴", -"舉"=>"举", -"舊"=>"旧", -"艙"=>"舱", -"艤"=>"舣", -"艦"=>"舰", -"艫"=>"舻", -"艱"=>"艰", -"艷"=>"艳", -"芻"=>"刍", -"苧"=>"苎", -"苹"=>"苹", -"茲"=>"兹", -"荊"=>"荆", -"莊"=>"庄", -"莖"=>"茎", -"莢"=>"荚", -"莧"=>"苋", -"華"=>"华", -"萇"=>"苌", -"萊"=>"莱", -"萬"=>"万", -"萵"=>"莴", -"葉"=>"叶", -"葒"=>"荭", -"葤"=>"荮", -"葦"=>"苇", -"葯"=>"药", -"葷"=>"荤", -"蒓"=>"莼", -"蒔"=>"莳", -"蒞"=>"莅", -"蒼"=>"苍", -"蓀"=>"荪", -"蓋"=>"盖", -"蓮"=>"莲", -"蓯"=>"苁", -"蓴"=>"莼", -"蓽"=>"荜", -"蔔"=>"卜", -"蔘"=>"参", -"蔞"=>"蒌", -"蔣"=>"蒋", -"蔥"=>"葱", -"蔦"=>"茑", -"蔭"=>"荫", -"蕁"=>"荨", -"蕆"=>"蒇", -"蕎"=>"荞", -"蕒"=>"荬", -"蕓"=>"芸", -"蕕"=>"莸", -"蕘"=>"荛", -"蕢"=>"蒉", -"蕩"=>"荡", -"蕪"=>"芜", -"蕭"=>"萧", -"蕷"=>"蓣", -"薀"=>"蕰", -"薈"=>"荟", -"薊"=>"蓟", -"薌"=>"芗", -"薑"=>"姜", -"薔"=>"蔷", -"薘"=>"荙", -"薟"=>"莶", -"薦"=>"荐", -"薩"=>"萨", -"薳"=>"䓕", -"薴"=>"苧", -"薺"=>"荠", -"藍"=>"蓝", -"藎"=>"荩", -"藝"=>"艺", -"藥"=>"药", -"藪"=>"薮", -"藴"=>"蕴", -"藶"=>"苈", -"藹"=>"蔼", -"藺"=>"蔺", -"蘄"=>"蕲", -"蘆"=>"芦", -"蘇"=>"苏", -"蘊"=>"蕴", -"蘋"=>"苹", -"蘚"=>"藓", -"蘞"=>"蔹", -"蘢"=>"茏", -"蘭"=>"兰", -"蘺"=>"蓠", -"蘿"=>"萝", -"虆"=>"蔂", -"處"=>"处", -"虛"=>"虚", -"虜"=>"虏", -"號"=>"号", -"虧"=>"亏", -"虫"=>"虫", -"虯"=>"虬", -"蛺"=>"蛱", -"蛻"=>"蜕", -"蜆"=>"蚬", -"蜡"=>"蜡", -"蝕"=>"蚀", -"蝟"=>"猬", -"蝦"=>"虾", -"蝸"=>"蜗", -"螄"=>"蛳", -"螞"=>"蚂", -"螢"=>"萤", -"螮"=>"䗖", -"螻"=>"蝼", -"螿"=>"螀", -"蟄"=>"蛰", -"蟈"=>"蝈", -"蟎"=>"螨", -"蟣"=>"虮", -"蟬"=>"蝉", -"蟯"=>"蛲", -"蟲"=>"虫", -"蟶"=>"蛏", -"蟻"=>"蚁", -"蠅"=>"蝇", -"蠆"=>"虿", -"蠐"=>"蛴", -"蠑"=>"蝾", -"蠟"=>"蜡", -"蠣"=>"蛎", -"蠨"=>"蟏", -"蠱"=>"蛊", -"蠶"=>"蚕", -"蠻"=>"蛮", -"衆"=>"众", -"衊"=>"蔑", -"術"=>"术", -"衕"=>"同", -"衚"=>"胡", -"衛"=>"卫", -"衝"=>"冲", -"衹"=>"只", -"袞"=>"衮", -"裊"=>"袅", -"裏"=>"里", -"補"=>"补", -"裝"=>"装", -"裡"=>"里", -"製"=>"制", -"複"=>"复", -"褌"=>"裈", -"褘"=>"袆", -"褲"=>"裤", -"褳"=>"裢", -"褸"=>"褛", -"褻"=>"亵", -"襇"=>"裥", -"襏"=>"袯", -"襖"=>"袄", -"襝"=>"裣", -"襠"=>"裆", -"襤"=>"褴", -"襪"=>"袜", -"襬"=>"䙓", -"襯"=>"衬", -"襲"=>"袭", -"覆"=>"复", -"見"=>"见", -"覎"=>"觃", -"規"=>"规", -"覓"=>"觅", -"視"=>"视", -"覘"=>"觇", -"覡"=>"觋", -"覥"=>"觍", -"覦"=>"觎", -"親"=>"亲", -"覬"=>"觊", -"覯"=>"觏", -"覲"=>"觐", -"覷"=>"觑", -"覺"=>"觉", -"覽"=>"览", -"覿"=>"觌", -"觀"=>"观", -"觴"=>"觞", -"觶"=>"觯", -"觸"=>"触", -"訁"=>"讠", -"訂"=>"订", -"訃"=>"讣", -"計"=>"计", -"訊"=>"讯", -"訌"=>"讧", -"討"=>"讨", -"訐"=>"讦", -"訒"=>"讱", -"訓"=>"训", -"訕"=>"讪", -"訖"=>"讫", -"託"=>"托", -"託"=>"讬", -"記"=>"记", -"訛"=>"讹", -"訝"=>"讶", -"訟"=>"讼", -"訢"=>"䜣", -"訣"=>"诀", -"訥"=>"讷", -"訩"=>"讻", -"訪"=>"访", -"設"=>"设", -"許"=>"许", -"訴"=>"诉", -"訶"=>"诃", -"診"=>"诊", -"註"=>"注", -"詁"=>"诂", -"詆"=>"诋", -"詎"=>"讵", -"詐"=>"诈", -"詒"=>"诒", -"詔"=>"诏", -"評"=>"评", -"詖"=>"诐", -"詗"=>"诇", -"詘"=>"诎", -"詛"=>"诅", -"詞"=>"词", -"詠"=>"咏", -"詡"=>"诩", -"詢"=>"询", -"詣"=>"诣", -"試"=>"试", -"詩"=>"诗", -"詫"=>"诧", -"詬"=>"诟", -"詭"=>"诡", -"詮"=>"诠", -"詰"=>"诘", -"話"=>"话", -"該"=>"该", -"詳"=>"详", -"詵"=>"诜", -"詼"=>"诙", -"詿"=>"诖", -"誄"=>"诔", -"誅"=>"诛", -"誆"=>"诓", -"誇"=>"夸", -"誌"=>"志", -"認"=>"认", -"誑"=>"诳", -"誒"=>"诶", -"誕"=>"诞", -"誘"=>"诱", -"誚"=>"诮", -"語"=>"语", -"誠"=>"诚", -"誡"=>"诫", -"誣"=>"诬", -"誤"=>"误", -"誥"=>"诰", -"誦"=>"诵", -"誨"=>"诲", -"說"=>"说", -"説"=>"说", -"誰"=>"谁", -"課"=>"课", -"誶"=>"谇", -"誹"=>"诽", -"誼"=>"谊", -"誾"=>"訚", -"調"=>"调", -"諂"=>"谄", -"諄"=>"谆", -"談"=>"谈", -"諉"=>"诿", -"請"=>"请", -"諍"=>"诤", -"諏"=>"诹", -"諑"=>"诼", -"諒"=>"谅", -"論"=>"论", -"諗"=>"谂", -"諛"=>"谀", -"諜"=>"谍", -"諝"=>"谞", -"諞"=>"谝", -"諢"=>"诨", -"諤"=>"谔", -"諦"=>"谛", -"諧"=>"谐", -"諫"=>"谏", -"諭"=>"谕", -"諮"=>"谘", -"諱"=>"讳", -"諳"=>"谙", -"諶"=>"谌", -"諷"=>"讽", -"諸"=>"诸", -"諺"=>"谚", -"諼"=>"谖", -"諾"=>"诺", -"謀"=>"谋", -"謁"=>"谒", -"謂"=>"谓", -"謄"=>"誊", -"謅"=>"诌", -"謊"=>"谎", -"謎"=>"谜", -"謐"=>"谧", -"謔"=>"谑", -"謖"=>"谡", -"謗"=>"谤", -"謙"=>"谦", -"謚"=>"谥", -"講"=>"讲", -"謝"=>"谢", -"謠"=>"谣", -"謡"=>"谣", -"謨"=>"谟", -"謫"=>"谪", -"謬"=>"谬", -"謭"=>"谫", -"謳"=>"讴", -"謹"=>"谨", -"謾"=>"谩", -"譅"=>"䜧", -"證"=>"证", -"譎"=>"谲", -"譏"=>"讥", -"譖"=>"谮", -"識"=>"识", -"譙"=>"谯", -"譚"=>"谭", -"譜"=>"谱", -"譫"=>"谵", -"譭"=>"毁", -"譯"=>"译", -"議"=>"议", -"譴"=>"谴", -"護"=>"护", -"譸"=>"诪", -"譽"=>"誉", -"譾"=>"谫", -"讀"=>"读", -"變"=>"变", -"讎"=>"仇", -"讎"=>"雠", -"讒"=>"谗", -"讓"=>"让", -"讕"=>"谰", -"讖"=>"谶", -"讚"=>"赞", -"讜"=>"谠", -"讞"=>"谳", -"豈"=>"岂", -"豎"=>"竖", -"豐"=>"丰", -"豬"=>"猪", -"豶"=>"豮", -"貓"=>"猫", -"貙"=>"䝙", -"貝"=>"贝", -"貞"=>"贞", -"貟"=>"贠", -"負"=>"负", -"財"=>"财", -"貢"=>"贡", -"貧"=>"贫", -"貨"=>"货", -"販"=>"贩", -"貪"=>"贪", -"貫"=>"贯", -"責"=>"责", -"貯"=>"贮", -"貰"=>"贳", -"貲"=>"赀", -"貳"=>"贰", -"貴"=>"贵", -"貶"=>"贬", -"買"=>"买", -"貸"=>"贷", -"貺"=>"贶", -"費"=>"费", -"貼"=>"贴", -"貽"=>"贻", -"貿"=>"贸", -"賀"=>"贺", -"賁"=>"贲", -"賂"=>"赂", -"賃"=>"赁", -"賄"=>"贿", -"賅"=>"赅", -"資"=>"资", -"賈"=>"贾", -"賊"=>"贼", -"賑"=>"赈", -"賒"=>"赊", -"賓"=>"宾", -"賕"=>"赇", -"賙"=>"赒", -"賚"=>"赉", -"賜"=>"赐", -"賞"=>"赏", -"賠"=>"赔", -"賡"=>"赓", -"賢"=>"贤", -"賣"=>"卖", -"賤"=>"贱", -"賦"=>"赋", -"賧"=>"赕", -"質"=>"质", -"賫"=>"赍", -"賬"=>"账", -"賭"=>"赌", -"賰"=>"䞐", -"賴"=>"赖", -"賵"=>"赗", -"賺"=>"赚", -"賻"=>"赙", -"購"=>"购", -"賽"=>"赛", -"賾"=>"赜", -"贄"=>"贽", -"贅"=>"赘", -"贇"=>"赟", -"贈"=>"赠", -"贊"=>"赞", -"贋"=>"赝", -"贍"=>"赡", -"贏"=>"赢", -"贐"=>"赆", -"贓"=>"赃", -"贔"=>"赑", -"贖"=>"赎", -"贗"=>"赝", -"贛"=>"赣", -"贜"=>"赃", -"赬"=>"赪", -"趕"=>"赶", -"趙"=>"赵", -"趨"=>"趋", -"趲"=>"趱", -"跡"=>"迹", -"踐"=>"践", -"踴"=>"踊", -"蹌"=>"跄", -"蹕"=>"跸", -"蹣"=>"蹒", -"蹤"=>"踪", -"蹺"=>"跷", -"躂"=>"跶", -"躉"=>"趸", -"躊"=>"踌", -"躋"=>"跻", -"躍"=>"跃", -"躑"=>"踯", -"躒"=>"跞", -"躓"=>"踬", -"躕"=>"蹰", -"躚"=>"跹", -"躡"=>"蹑", -"躥"=>"蹿", -"躦"=>"躜", -"躪"=>"躏", -"軀"=>"躯", -"車"=>"车", -"軋"=>"轧", -"軌"=>"轨", -"軍"=>"军", -"軑"=>"轪", -"軒"=>"轩", -"軔"=>"轫", -"軛"=>"轭", -"軟"=>"软", -"軤"=>"轷", -"軫"=>"轸", -"軲"=>"轱", -"軸"=>"轴", -"軹"=>"轵", -"軺"=>"轺", -"軻"=>"轲", -"軼"=>"轶", -"軾"=>"轼", -"較"=>"较", -"輅"=>"辂", -"輇"=>"辁", -"輈"=>"辀", -"載"=>"载", -"輊"=>"轾", -"輒"=>"辄", -"輓"=>"挽", -"輔"=>"辅", -"輕"=>"轻", -"輛"=>"辆", -"輜"=>"辎", -"輝"=>"辉", -"輞"=>"辋", -"輟"=>"辍", -"輥"=>"辊", -"輦"=>"辇", -"輩"=>"辈", -"輪"=>"轮", -"輬"=>"辌", -"輯"=>"辑", -"輳"=>"辏", -"輸"=>"输", -"輻"=>"辐", -"輾"=>"辗", -"輿"=>"舆", -"轀"=>"辒", -"轂"=>"毂", -"轄"=>"辖", -"轅"=>"辕", -"轆"=>"辘", -"轉"=>"转", -"轍"=>"辙", -"轎"=>"轿", -"轔"=>"辚", -"轟"=>"轰", -"轡"=>"辔", -"轢"=>"轹", -"轤"=>"轳", -"辦"=>"办", -"辭"=>"辞", -"辮"=>"辫", -"辯"=>"辩", -"農"=>"农", -"迴"=>"回", -"适"=>"适", -"逕"=>"迳", -"這"=>"这", -"連"=>"连", -"週"=>"周", -"進"=>"进", -"遊"=>"游", -"運"=>"运", -"過"=>"过", -"達"=>"达", -"違"=>"违", -"遙"=>"遥", -"遜"=>"逊", -"遞"=>"递", -"遠"=>"远", -"適"=>"适", -"遲"=>"迟", -"遷"=>"迁", -"選"=>"选", -"遺"=>"遗", -"遼"=>"辽", -"邁"=>"迈", -"還"=>"还", -"邇"=>"迩", -"邊"=>"边", -"邏"=>"逻", -"邐"=>"逦", -"郟"=>"郏", -"郵"=>"邮", -"鄆"=>"郓", -"鄉"=>"乡", -"鄒"=>"邹", -"鄔"=>"邬", -"鄖"=>"郧", -"鄧"=>"邓", -"鄭"=>"郑", -"鄰"=>"邻", -"鄲"=>"郸", -"鄴"=>"邺", -"鄶"=>"郐", -"鄺"=>"邝", -"酇"=>"酂", -"酈"=>"郦", -"醖"=>"酝", -"醜"=>"丑", -"醞"=>"酝", -"醫"=>"医", -"醬"=>"酱", -"醱"=>"酦", -"釀"=>"酿", -"釁"=>"衅", -"釃"=>"酾", -"釅"=>"酽", -"釋"=>"释", -"釐"=>"厘", -"釒"=>"钅", -"釓"=>"钆", -"釔"=>"钇", -"釕"=>"钌", -"釗"=>"钊", -"釘"=>"钉", -"釙"=>"钋", -"針"=>"针", -"釣"=>"钓", -"釤"=>"钐", -"釧"=>"钏", -"釩"=>"钒", -"釵"=>"钗", -"釷"=>"钍", -"釹"=>"钕", -"釺"=>"钎", -"鈀"=>"钯", -"鈁"=>"钫", -"鈃"=>"钘", -"鈄"=>"钭", -"鈈"=>"钚", -"鈉"=>"钠", -"鈍"=>"钝", -"鈎"=>"钩", -"鈐"=>"钤", -"鈑"=>"钣", -"鈒"=>"钑", -"鈔"=>"钞", -"鈕"=>"钮", -"鈞"=>"钧", -"鈣"=>"钙", -"鈥"=>"钬", -"鈦"=>"钛", -"鈧"=>"钪", -"鈮"=>"铌", -"鈰"=>"铈", -"鈳"=>"钶", -"鈴"=>"铃", -"鈷"=>"钴", -"鈸"=>"钹", -"鈹"=>"铍", -"鈺"=>"钰", -"鈽"=>"钸", -"鈾"=>"铀", -"鈿"=>"钿", -"鉀"=>"钾", -"鉅"=>"钜", -"鉈"=>"铊", -"鉉"=>"铉", -"鉋"=>"铇", -"鉍"=>"铋", -"鉑"=>"铂", -"鉕"=>"钷", -"鉗"=>"钳", -"鉚"=>"铆", -"鉛"=>"铅", -"鉞"=>"钺", -"鉢"=>"钵", -"鉤"=>"钩", -"鉦"=>"钲", -"鉬"=>"钼", -"鉭"=>"钽", -"鉶"=>"铏", -"鉸"=>"铰", -"鉺"=>"铒", -"鉻"=>"铬", -"鉿"=>"铪", -"銀"=>"银", -"銃"=>"铳", -"銅"=>"铜", -"銍"=>"铚", -"銑"=>"铣", -"銓"=>"铨", -"銖"=>"铢", -"銘"=>"铭", -"銚"=>"铫", -"銛"=>"铦", -"銜"=>"衔", -"銠"=>"铑", -"銣"=>"铷", -"銥"=>"铱", -"銦"=>"铟", -"銨"=>"铵", -"銩"=>"铥", -"銪"=>"铕", -"銫"=>"铯", -"銬"=>"铐", -"銱"=>"铞", -"銳"=>"锐", -"銷"=>"销", -"銹"=>"锈", -"銻"=>"锑", -"銼"=>"锉", -"鋁"=>"铝", -"鋃"=>"锒", -"鋅"=>"锌", -"鋇"=>"钡", -"鋌"=>"铤", -"鋏"=>"铗", -"鋒"=>"锋", -"鋙"=>"铻", -"鋝"=>"锊", -"鋟"=>"锓", -"鋣"=>"铘", -"鋤"=>"锄", -"鋥"=>"锃", -"鋦"=>"锔", -"鋨"=>"锇", -"鋩"=>"铓", -"鋪"=>"铺", -"鋭"=>"锐", -"鋮"=>"铖", -"鋯"=>"锆", -"鋰"=>"锂", -"鋱"=>"铽", -"鋶"=>"锍", -"鋸"=>"锯", -"鋼"=>"钢", -"錁"=>"锞", -"錄"=>"录", -"錆"=>"锖", -"錇"=>"锫", -"錈"=>"锩", -"錏"=>"铔", -"錐"=>"锥", -"錒"=>"锕", -"錕"=>"锟", -"錘"=>"锤", -"錙"=>"锱", -"錚"=>"铮", -"錛"=>"锛", -"錟"=>"锬", -"錠"=>"锭", -"錡"=>"锜", -"錢"=>"钱", -"錦"=>"锦", -"錨"=>"锚", -"錩"=>"锠", -"錫"=>"锡", -"錮"=>"锢", -"錯"=>"错", -"録"=>"录", -"錳"=>"锰", -"錶"=>"表", -"錸"=>"铼", -"鍀"=>"锝", -"鍁"=>"锨", -"鍃"=>"锪", -"鍆"=>"钔", -"鍇"=>"锴", -"鍈"=>"锳", -"鍋"=>"锅", -"鍍"=>"镀", -"鍔"=>"锷", -"鍘"=>"铡", -"鍚"=>"钖", -"鍛"=>"锻", -"鍠"=>"锽", -"鍤"=>"锸", -"鍥"=>"锲", -"鍩"=>"锘", -"鍬"=>"锹", -"鍰"=>"锾", -"鍵"=>"键", -"鍶"=>"锶", -"鍺"=>"锗", -"鎂"=>"镁", -"鎄"=>"锿", -"鎇"=>"镅", -"鎊"=>"镑", -"鎔"=>"镕", -"鎖"=>"锁", -"鎘"=>"镉", -"鎚"=>"锤", -"鎛"=>"镈", -"鎝"=>"𨱏", -"鎡"=>"镃", -"鎢"=>"钨", -"鎣"=>"蓥", -"鎦"=>"镏", -"鎧"=>"铠", -"鎩"=>"铩", -"鎪"=>"锼", -"鎬"=>"镐", -"鎮"=>"镇", -"鎰"=>"镒", -"鎲"=>"镋", -"鎳"=>"镍", -"鎵"=>"镓", -"鎸"=>"镌", -"鎿"=>"镎", -"鏃"=>"镞", -"鏇"=>"镟", -"鏈"=>"链", -"鏌"=>"镆", -"鏍"=>"镙", -"鏐"=>"镠", -"鏑"=>"镝", -"鏗"=>"铿", -"鏘"=>"锵", -"鏜"=>"镗", -"鏝"=>"镘", -"鏞"=>"镛", -"鏟"=>"铲", -"鏡"=>"镜", -"鏢"=>"镖", -"鏤"=>"镂", -"鏨"=>"錾", -"鏰"=>"镚", -"鏵"=>"铧", -"鏷"=>"镤", -"鏹"=>"镪", -"鏽"=>"锈", -"鐃"=>"铙", -"鐋"=>"铴", -"鐐"=>"镣", -"鐒"=>"铹", -"鐓"=>"镦", -"鐔"=>"镡", -"鐘"=>"钟", -"鐙"=>"镫", -"鐝"=>"镢", -"鐠"=>"镨", -"鐦"=>"锎", -"鐧"=>"锏", -"鐨"=>"镄", -"鐫"=>"镌", -"鐮"=>"镰", -"鐲"=>"镯", -"鐳"=>"镭", -"鐵"=>"铁", -"鐶"=>"镮", -"鐸"=>"铎", -"鐺"=>"铛", -"鐿"=>"镱", -"鑄"=>"铸", -"鑊"=>"镬", -"鑌"=>"镔", -"鑒"=>"鉴", -"鑔"=>"镲", -"鑕"=>"锧", -"鑞"=>"镴", -"鑠"=>"铄", -"鑣"=>"镳", -"鑥"=>"镥", -"鑭"=>"镧", -"鑰"=>"钥", -"鑱"=>"镵", -"鑲"=>"镶", -"鑷"=>"镊", -"鑹"=>"镩", -"鑼"=>"锣", -"鑽"=>"钻", -"鑾"=>"銮", -"鑿"=>"凿", -"钁"=>"镢", -"镟"=>"旋", -"長"=>"长", -"門"=>"门", -"閂"=>"闩", -"閃"=>"闪", -"閆"=>"闫", -"閈"=>"闬", -"閉"=>"闭", -"開"=>"开", -"閌"=>"闶", -"閎"=>"闳", -"閏"=>"闰", -"閑"=>"闲", -"閒"=>"闲", -"間"=>"间", -"閔"=>"闵", -"閘"=>"闸", -"閡"=>"阂", -"閣"=>"阁", -"閤"=>"合", -"閥"=>"阀", -"閨"=>"闺", -"閩"=>"闽", -"閫"=>"阃", -"閬"=>"阆", -"閭"=>"闾", -"閱"=>"阅", -"閲"=>"阅", -"閶"=>"阊", -"閹"=>"阉", -"閻"=>"阎", -"閼"=>"阏", -"閽"=>"阍", -"閾"=>"阈", -"閿"=>"阌", -"闃"=>"阒", -"闆"=>"板", -"闈"=>"闱", -"闊"=>"阔", -"闋"=>"阕", -"闌"=>"阑", -"闍"=>"阇", -"闐"=>"阗", -"闒"=>"阘", -"闓"=>"闿", -"闔"=>"阖", -"闕"=>"阙", -"闖"=>"闯", -"關"=>"关", -"闞"=>"阚", -"闠"=>"阓", -"闡"=>"阐", -"闢"=>"辟", -"闤"=>"阛", -"闥"=>"闼", -"阪"=>"坂", -"陘"=>"陉", -"陝"=>"陕", -"陞"=>"升", -"陣"=>"阵", -"陰"=>"阴", -"陳"=>"陈", -"陸"=>"陆", -"陽"=>"阳", -"隉"=>"陧", -"隊"=>"队", -"階"=>"阶", -"隕"=>"陨", -"際"=>"际", -"隨"=>"随", -"險"=>"险", -"隱"=>"隐", -"隴"=>"陇", -"隸"=>"隶", -"隻"=>"只", -"雋"=>"隽", -"雖"=>"虽", -"雙"=>"双", -"雛"=>"雏", -"雜"=>"杂", -"雞"=>"鸡", -"離"=>"离", -"難"=>"难", -"雲"=>"云", -"電"=>"电", -"霢"=>"霡", -"霧"=>"雾", -"霽"=>"霁", -"靂"=>"雳", -"靄"=>"霭", -"靈"=>"灵", -"靚"=>"靓", -"靜"=>"静", -"靨"=>"靥", -"鞀"=>"鼗", -"鞏"=>"巩", -"鞝"=>"绱", -"鞦"=>"秋", -"鞽"=>"鞒", -"韁"=>"缰", -"韃"=>"鞑", -"韆"=>"千", -"韉"=>"鞯", -"韋"=>"韦", -"韌"=>"韧", -"韍"=>"韨", -"韓"=>"韩", -"韙"=>"韪", -"韜"=>"韬", -"韞"=>"韫", -"韻"=>"韵", -"響"=>"响", -"頁"=>"页", -"頂"=>"顶", -"頃"=>"顷", -"項"=>"项", -"順"=>"顺", -"頇"=>"顸", -"須"=>"须", -"頊"=>"顼", -"頌"=>"颂", -"頎"=>"颀", -"頏"=>"颃", -"預"=>"预", -"頑"=>"顽", -"頒"=>"颁", -"頓"=>"顿", -"頗"=>"颇", -"領"=>"领", -"頜"=>"颌", -"頡"=>"颉", -"頤"=>"颐", -"頦"=>"颏", -"頭"=>"头", -"頮"=>"颒", -"頰"=>"颊", -"頲"=>"颋", -"頴"=>"颕", -"頷"=>"颔", -"頸"=>"颈", -"頹"=>"颓", -"頻"=>"频", -"頽"=>"颓", -"顆"=>"颗", -"題"=>"题", -"額"=>"额", -"顎"=>"颚", -"顏"=>"颜", -"顒"=>"颙", -"顓"=>"颛", -"顔"=>"颜", -"願"=>"愿", -"顙"=>"颡", -"顛"=>"颠", -"類"=>"类", -"顢"=>"颟", -"顥"=>"颢", -"顧"=>"顾", -"顫"=>"颤", -"顬"=>"颥", -"顯"=>"显", -"顰"=>"颦", -"顱"=>"颅", -"顳"=>"颞", -"顴"=>"颧", -"風"=>"风", -"颭"=>"飐", -"颮"=>"飑", -"颯"=>"飒", -"颱"=>"台", -"颳"=>"刮", -"颶"=>"飓", -"颸"=>"飔", -"颺"=>"飏", -"颻"=>"飖", -"颼"=>"飕", -"飀"=>"飗", -"飄"=>"飘", -"飆"=>"飙", -"飈"=>"飚", -"飛"=>"飞", -"飠"=>"饣", -"飢"=>"饥", -"飣"=>"饤", -"飥"=>"饦", -"飩"=>"饨", -"飪"=>"饪", -"飫"=>"饫", -"飭"=>"饬", -"飯"=>"饭", -"飲"=>"饮", -"飴"=>"饴", -"飼"=>"饲", -"飽"=>"饱", -"飾"=>"饰", -"飿"=>"饳", -"餃"=>"饺", -"餄"=>"饸", -"餅"=>"饼", -"餉"=>"饷", -"養"=>"养", -"餌"=>"饵", -"餎"=>"饹", -"餏"=>"饻", -"餑"=>"饽", -"餒"=>"馁", -"餓"=>"饿", -"餕"=>"馂", -"餖"=>"饾", -"餘"=>"余", -"餚"=>"肴", -"餛"=>"馄", -"餜"=>"馃", -"餞"=>"饯", -"餡"=>"馅", -"館"=>"馆", -"餱"=>"糇", -"餳"=>"饧", -"餵"=>"喂", -"餶"=>"馉", -"餷"=>"馇", -"餺"=>"馎", -"餼"=>"饩", -"餾"=>"馏", -"餿"=>"馊", -"饁"=>"馌", -"饃"=>"馍", -"饅"=>"馒", -"饈"=>"馐", -"饉"=>"馑", -"饊"=>"馓", -"饋"=>"馈", -"饌"=>"馔", -"饑"=>"饥", -"饒"=>"饶", -"饗"=>"飨", -"饜"=>"餍", -"饞"=>"馋", -"饢"=>"馕", -"馬"=>"马", -"馭"=>"驭", -"馮"=>"冯", -"馱"=>"驮", -"馳"=>"驰", -"馴"=>"驯", -"馹"=>"驲", -"駁"=>"驳", -"駐"=>"驻", -"駑"=>"驽", -"駒"=>"驹", -"駔"=>"驵", -"駕"=>"驾", -"駘"=>"骀", -"駙"=>"驸", -"駛"=>"驶", -"駝"=>"驼", -"駟"=>"驷", -"駡"=>"骂", -"駢"=>"骈", -"駭"=>"骇", -"駰"=>"骃", -"駱"=>"骆", -"駸"=>"骎", -"駿"=>"骏", -"騁"=>"骋", -"騂"=>"骍", -"騅"=>"骓", -"騌"=>"骔", -"騍"=>"骒", -"騎"=>"骑", -"騏"=>"骐", -"騖"=>"骛", -"騙"=>"骗", -"騤"=>"骙", -"騧"=>"䯄", -"騫"=>"骞", -"騭"=>"骘", -"騮"=>"骝", -"騰"=>"腾", -"騶"=>"驺", -"騷"=>"骚", -"騸"=>"骟", -"騾"=>"骡", -"驀"=>"蓦", -"驁"=>"骜", -"驂"=>"骖", -"驃"=>"骠", -"驄"=>"骢", -"驅"=>"驱", -"驊"=>"骅", -"驌"=>"骕", -"驍"=>"骁", -"驏"=>"骣", -"驕"=>"骄", -"驗"=>"验", -"驚"=>"惊", -"驛"=>"驿", -"驟"=>"骤", -"驢"=>"驴", -"驤"=>"骧", -"驥"=>"骥", -"驦"=>"骦", -"驪"=>"骊", -"驫"=>"骉", -"骯"=>"肮", -"髏"=>"髅", -"髒"=>"脏", -"體"=>"体", -"髕"=>"髌", -"髖"=>"髋", -"髮"=>"发", -"鬆"=>"松", -"鬍"=>"胡", -"鬚"=>"须", -"鬢"=>"鬓", -"鬥"=>"斗", -"鬧"=>"闹", -"鬨"=>"哄", -"鬩"=>"阋", -"鬮"=>"阄", -"鬱"=>"郁", -"魎"=>"魉", -"魘"=>"魇", -"魚"=>"鱼", -"魛"=>"鱽", -"魢"=>"鱾", -"魨"=>"鲀", -"魯"=>"鲁", -"魴"=>"鲂", -"魷"=>"鱿", -"魺"=>"鲄", -"鮁"=>"鲅", -"鮃"=>"鲆", -"鮊"=>"鲌", -"鮋"=>"鲉", -"鮍"=>"鲏", -"鮎"=>"鲇", -"鮐"=>"鲐", -"鮑"=>"鲍", -"鮒"=>"鲋", -"鮓"=>"鲊", -"鮚"=>"鲒", -"鮜"=>"鲘", -"鮝"=>"鲞", -"鮞"=>"鲕", -"鮦"=>"鲖", -"鮪"=>"鲔", -"鮫"=>"鲛", -"鮭"=>"鲑", -"鮮"=>"鲜", -"鮳"=>"鲓", -"鮶"=>"鲪", -"鮺"=>"鲝", -"鯀"=>"鲧", -"鯁"=>"鲠", -"鯇"=>"鲩", -"鯉"=>"鲤", -"鯊"=>"鲨", -"鯒"=>"鲬", -"鯔"=>"鲻", -"鯕"=>"鲯", -"鯖"=>"鲭", -"鯗"=>"鲞", -"鯛"=>"鲷", -"鯝"=>"鲴", -"鯡"=>"鲱", -"鯢"=>"鲵", -"鯤"=>"鲲", -"鯧"=>"鲳", -"鯨"=>"鲸", -"鯪"=>"鲮", -"鯫"=>"鲰", -"鯴"=>"鲺", -"鯷"=>"鳀", -"鯽"=>"鲫", -"鯿"=>"鳊", -"鰁"=>"鳈", -"鰂"=>"鲗", -"鰃"=>"鳂", -"鰈"=>"鲽", -"鰉"=>"鳇", -"鰍"=>"鳅", -"鰏"=>"鲾", -"鰐"=>"鳄", -"鰒"=>"鳆", -"鰓"=>"鳃", -"鰜"=>"鳒", -"鰟"=>"鳑", -"鰠"=>"鳋", -"鰣"=>"鲥", -"鰥"=>"鳏", -"鰨"=>"鳎", -"鰩"=>"鳐", -"鰭"=>"鳍", -"鰮"=>"鳁", -"鰱"=>"鲢", -"鰲"=>"鳌", -"鰳"=>"鳓", -"鰵"=>"鳘", -"鰷"=>"鲦", -"鰹"=>"鲣", -"鰺"=>"鲹", -"鰻"=>"鳗", -"鰼"=>"鳛", -"鰾"=>"鳔", -"鱂"=>"鳉", -"鱅"=>"鳙", -"鱈"=>"鳕", -"鱉"=>"鳖", -"鱒"=>"鳟", -"鱔"=>"鳝", -"鱖"=>"鳜", -"鱗"=>"鳞", -"鱘"=>"鲟", -"鱝"=>"鲼", -"鱟"=>"鲎", -"鱠"=>"鲙", -"鱣"=>"鳣", -"鱤"=>"鳡", -"鱧"=>"鳢", -"鱨"=>"鲿", -"鱭"=>"鲚", -"鱯"=>"鳠", -"鱷"=>"鳄", -"鱸"=>"鲈", -"鱺"=>"鲡", -"䰾"=>"鲃", -"䲁"=>"鳚", -"鳥"=>"鸟", -"鳧"=>"凫", -"鳩"=>"鸠", -"鳬"=>"凫", -"鳲"=>"鸤", -"鳳"=>"凤", -"鳴"=>"鸣", -"鳶"=>"鸢", -"鳾"=>"䴓", -"鴆"=>"鸩", -"鴇"=>"鸨", -"鴉"=>"鸦", -"鴒"=>"鸰", -"鴕"=>"鸵", -"鴛"=>"鸳", -"鴝"=>"鸲", -"鴞"=>"鸮", -"鴟"=>"鸱", -"鴣"=>"鸪", -"鴦"=>"鸯", -"鴨"=>"鸭", -"鴯"=>"鸸", -"鴰"=>"鸹", -"鴴"=>"鸻", -"鴷"=>"䴕", -"鴻"=>"鸿", -"鴿"=>"鸽", -"鵁"=>"䴔", -"鵂"=>"鸺", -"鵃"=>"鸼", -"鵐"=>"鹀", -"鵑"=>"鹃", -"鵒"=>"鹆", -"鵓"=>"鹁", -"鵜"=>"鹈", -"鵝"=>"鹅", -"鵠"=>"鹄", -"鵡"=>"鹉", -"鵪"=>"鹌", -"鵬"=>"鹏", -"鵮"=>"鹐", -"鵯"=>"鹎", -"鵰"=>"雕", -"鵲"=>"鹊", -"鵷"=>"鹓", -"鵾"=>"鹍", -"鶄"=>"䴖", -"鶇"=>"鸫", -"鶉"=>"鹑", -"鶊"=>"鹒", -"鶓"=>"鹋", -"鶖"=>"鹙", -"鶘"=>"鹕", -"鶚"=>"鹗", -"鶡"=>"鹖", -"鶥"=>"鹛", -"鶩"=>"鹜", -"鶪"=>"䴗", -"鶬"=>"鸧", -"鶯"=>"莺", -"鶲"=>"鹟", -"鶴"=>"鹤", -"鶹"=>"鹠", -"鶺"=>"鹡", -"鶻"=>"鹘", -"鶼"=>"鹣", -"鶿"=>"鹚", -"鷀"=>"鹚", -"鷁"=>"鹢", -"鷂"=>"鹞", -"鷄"=>"鸡", -"鷈"=>"䴘", -"鷊"=>"鹝", -"鷓"=>"鹧", -"鷖"=>"鹥", -"鷗"=>"鸥", -"鷙"=>"鸷", -"鷚"=>"鹨", -"鷥"=>"鸶", -"鷦"=>"鹪", -"鷫"=>"鹔", -"鷯"=>"鹩", -"鷲"=>"鹫", -"鷳"=>"鹇", -"鷸"=>"鹬", -"鷹"=>"鹰", -"鷺"=>"鹭", -"鷽"=>"鸴", -"鷿"=>"䴙", -"鸂"=>"㶉", -"鸇"=>"鹯", -"鸌"=>"鹱", -"鸏"=>"鹲", -"鸕"=>"鸬", -"鸘"=>"鹴", -"鸚"=>"鹦", -"鸛"=>"鹳", -"鸝"=>"鹂", -"鸞"=>"鸾", -"鹵"=>"卤", -"鹹"=>"咸", -"鹺"=>"鹾", -"鹼"=>"硷", -"鹽"=>"盐", -"麗"=>"丽", -"麥"=>"麦", -"麩"=>"麸", -"麪"=>"面", -"麫"=>"面", -"麯"=>"曲", -"麵"=>"面", -"麼"=>"么", -"麽"=>"么", -"黃"=>"黄", -"黌"=>"黉", -"點"=>"点", -"黨"=>"党", -"黲"=>"黪", -"黴"=>"霉", -"黶"=>"黡", -"黷"=>"黩", -"黽"=>"黾", -"黿"=>"鼋", -"鼉"=>"鼍", -"鼕"=>"冬", -"鼴"=>"鼹", -"齊"=>"齐", -"齋"=>"斋", -"齎"=>"赍", -"齏"=>"齑", -"齒"=>"齿", -"齔"=>"龀", -"齕"=>"龁", -"齗"=>"龂", -"齙"=>"龅", -"齜"=>"龇", -"齟"=>"龃", -"齠"=>"龆", -"齡"=>"龄", -"齣"=>"出", -"齦"=>"龈", -"齪"=>"龊", -"齬"=>"龉", -"齲"=>"龋", -"齶"=>"腭", -"齷"=>"龌", -"龍"=>"龙", -"龎"=>"厐", -"龐"=>"庞", -"龔"=>"龚", -"龕"=>"龛", -"龜"=>"龟", - -"一畫" => "一画", -"一著不慎" => "一着不慎", -"三聯畫" => "三联画", -"上畫" => "上画", -"不著" => "不着", -"不著痕跡" => "不着痕迹", -"不著邊際" => "不着边际", -"與著" => "与着", -"與著作" => "与著作", -"與著名" => "与著名", -"與著者" => "与著者", -"丑著" => "丑着", -"臨著" => "临着", -"為著" => "为着", -"麗著" => "丽着", -"舉著" => "举着", -"樂著" => "乐着", -"樂著作" => "乐著作", -"乘著" => "乘着", -"書畫" => "书画", -"乾乾" => "乾乾", -"乾元" => "乾元", -"乾卦" => "乾卦", -"乾縣" => "乾县", -"乾嘉" => "乾嘉", -"乾圖" => "乾图", -"乾坤 " => "乾坤 ", -"乾宅" => "乾宅", -"乾斷" => "乾断", -"乾旦" => "乾旦", -"乾曜" => "乾曜", -"乾清宮" => "乾清宫", -"乾盛世" => "乾盛世", -"乾紅" => "乾红", -"乾綱" => "乾纲", -"乾象" => "乾象", -"乾造" => "乾造", -"乾陵" => "乾陵", -"乾隆" => "乾隆", -"爭著" => "争着", -"交叉著" => "交叉着", -"亮著" => "亮着", -"仗著" => "仗着", -"代表著" => "代表着", -"代表著作" => "代表著作", -"代表著名" => "代表著名", -"代表著者" => "代表著者", -"仰著" => "仰着", -"傷著" => "伤着", -"伴著" => "伴着", -"低著" => "低着", -"住著" => "住着", -"佛頭著糞" => "佛头着粪", -"作畫" => "作画", -"側著" => "侧着", -"保障著" => "保障着", -"保障著作" => "保障著作", -"保障著名" => "保障著名", -"保障著者" => "保障著者", -"信著" => "信着", -"候著" => "候着", -"借著" => "借着", -"偎著" => "偎着", -"做著" => "做着", -"停著" => "停着", -"偷著" => "偷着", -"先人著鞭" => "先人着鞭", -"先我著鞭" => "先我着鞭", -"光著" => "光着", -"光著作" => "光著作", -"入畫" => "入画", -"關著" => "关着", -"關著作" => "关著作", -"關著名" => "关著名", -"關著者" => "关著者", -"冀著" => "冀着", -"冒著" => "冒着", -"寫生畫" => "写生画", -"寫著" => "写着", -"寫著作" => "写著作", -"寫著名" => "写著名", -"沖著" => "冲着", -"涼著" => "凉着", -"幾畫" => "几画", -"憑著" => "凭着", -"制著" => "制着", -"刻畫" => "刻画", -"刻著" => "刻着", -"剃著" => "剃着", -"剪著" => "剪着", -"辦著" => "办着", -"動畫" => "动画", -"動著" => "动着", -"努力著" => "努力着", -"努著" => "努着", -"勾畫" => "勾画", -"包著" => "包着", -"單色畫" => "单色画", -"賣畫" => "卖画", -"卡通畫" => "卡通画", -"鹵鹼" => "卤碱", -"印著" => "印着", -"卷舌" => "卷舌", -"壓著" => "压着", -"原畫" => "原画", -"去著" => "去着", -"受著" => "受着", -"受著作" => "受著作", -"受著名" => "受著名", -"受著者" => "受著者", -"變著" => "变着", -"口鹼" => "口碱", -"古畫" => "古画", -"叫喊著說" => "叫喊着说", -"叫著" => "叫着", -"吃著" => "吃着", -"名畫" => "名画", -"向著" => "向着", -"嚇著" => "吓着", -"含著" => "含着", -"含著作" => "含著作", -"含著名" => "含著名", -"含著者" => "含著者", -"聽著" => "听着", -"聽著作" => "听著作", -"聽著名" => "听著名", -"聽著者" => "听著者", -"吸著劑" => "吸着剂", -"吸著物" => "吸着物", -"吹著" => "吹着", -"呆著" => "呆着", -"嗆著" => "呛着", -"味著" => "味着", -"咧著" => "咧着", -"咬著" => "咬着", -"響著" => "响着", -"哭喪著臉" => "哭丧着脸", -"哭著" => "哭着", -"哼著唱" => "哼着唱", -"唱著" => "唱着", -"啃著" => "啃着", -"喝著" => "喝着", -"噙著" => "噙着", -"嚷著" => "嚷着", -"囔著" => "囔着", -"因著" => "因着", -"困著" => "困着", -"圍著" => "围着", -"固著" => "固着", -"國畫" => "国画", -"圖畫" => "图画", -"在著" => "在着", -"在著作" => "在著作", -"在著名" => "在著名", -"在著者" => "在著者", -"坐著" => "坐着", -"墊著" => "垫着", -"埋著" => "埋着", -"壁畫" => "壁画", -"備著" => "备着", -"失著" => "失着", -"夾著" => "夹着", -"奇畫" => "奇画", -"如畫" => "如画", -"字畫" => "字画", -"孤著" => "孤着", -"學著" => "学着", -"學著作" => "学著作", -"守著" => "守着", -"定著" => "定着", -"定著作" => "定著作", -"宣傳畫" => "宣传画", -"對著" => "对着", -"對著干" => "对着干", -"對著作" => "对著作", -"對著名" => "对著名", -"對著者" => "对著者", -"尋著" => "寻着", -"就著" => "就着", -"展著" => "展着", -"帶著" => "带着", -"幫著" => "帮着", -"年畫" => "年画", -"幽默畫" => "幽默画", -"應著" => "应着", -"康乾" => "康乾", -"康著" => "康着", -"開著" => "开着", -"弄著" => "弄着", -"引著" => "引着", -"張法乾" => "张法乾", -"弱鹼" => "弱碱", -"彈著點 " => "弹着点 ", -"歸著 " => "归着 ", -"當著" => "当着", -"當著 " => "当着 ", -"彩畫" => "彩画", -"待著" => "待着", -"得著" => "得着", -"得著作" => "得著作", -"得著名" => "得著名", -"得著者" => "得著者", -"循著" => "循着", -"心著" => "心着", -"忍著" => "忍着", -"志著" => "志着", -"忙著" => "忙着", -"念著" => "念着", -"懷著" => "怀着", -"怎么著" => "怎么着", -"急著" => "急着", -"性著" => "性着", -"性著作" => "性著作", -"性著名" => "性著名", -"性著者" => "性著者", -"性著述" => "性著述", -"戀著" => "恋着", -"悠著" => "悠着", -"懸著" => "悬着", -"惦著" => "惦着", -"慣著" => "惯着", -"想著" => "想着", -"意味著" => "意味着", -"愣著" => "愣着", -"慢著" => "慢着", -"憋著" => "憋着", -"戰著" => "战着", -"戴著" => "戴着", -"所畫" => "所画", -"扉畫" => "扉画", -"扎著" => "扎着", -"打著" => "打着", -"托著" => "托着", -"扛著" => "扛着", -"執著" => "执着", -"扭著" => "扭着", -"找不著" => "找不着", -"找著 " => "找着 ", -"抓著" => "抓着", -"抗著" => "抗着", -"搶著" => "抢着", -"披著" => "披着", -"抬著" => "抬着", -"抱著" => "抱着", -"押著" => "押着", -"拄著" => "拄着", -"拉著" => "拉着", -"拍著" => "拍着", -"拎著" => "拎着", -"拎著 " => "拎着 ", -"拖著" => "拖着", -"拙著" => "拙着", -"擁著" => "拥着", -"拼著" => "拼着", -"拽著" => "拽着", -"拿著" => "拿着", -"持著" => "持着", -"掛著" => "挂着", -"指畫" => "指画", -"指著" => "指着", -"按著" => "按着", -"挎著" => "挎着", -"挑著" => "挑着", -"擋著" => "挡着", -"掙著" => "挣着", -"擠著" => "挤着", -"揮著" => "挥着", -"挨著" => "挨着", -"挺著" => "挺着", -"捂著" => "捂着", -"捆著" => "捆着", -"捏著" => "捏着", -"撈著" => "捞着", -"撿著" => "捡着", -"捧著" => "捧着", -"據著" => "据着", -"據著作" => "据著作", -"據著名" => "据著名", -"據著者" => "据著者", -"掖著" => "掖着", -"接著" => "接着", -"推著" => "推着", -"揉著" => "揉着", -"描畫" => "描画", -"提著" => "提着", -"插畫" => "插画", -"握著" => "握着", -"摟著" => "搂着", -"擺著" => "摆着", -"搖著" => "摇着", -"摸著" => "摸着", -"撼著" => "撼着", -"擎著" => "擎着", -"擘畫 " => "擘画 ", -"攀著" => "攀着", -"攥著" => "攥着", -"支著" => "支着", -"放著" => "放着", -"教畫" => "教画", -"敞著" => "敞着", -"數著" => "数着", -"斗著" => "斗着", -"斥著" => "斥着", -"新著龍虎門" => "新著龙虎门", -"於乎" => "於乎", -"於夫羅" => "於夫罗", -"於姓" => "於姓", -"於戲" => "於戏", -"於梨華" => "於梨华", -"於氏" => "於氏", -"於潛縣" => "於潜县", -"於菟" => "於菟", -"旋乾轉坤" => "旋乾转坤", -"無著" => "无着", -"昂著" => "昂着", -"映著" => "映着", -"春畫" => "春画", -"昧著" => "昧着", -"晃著" => "晃着", -"暗著" => "暗着", -"有著" => "有着", -"有著作" => "有著作", -"有著名" => "有著名", -"有著者" => "有著者", -"望著" => "望着", -"朝著" => "朝着", -"本著" => "本着", -"本著作" => "本著作", -"本著名" => "本著名", -"本著者" => "本著者", -"機械畫" => "机械画", -"殺著" => "杀着", -"殺著作" => "杀著作", -"殺著名" => "杀著名", -"殺著者" => "杀著者", -"雜著" => "杂着", -"李乾德" => "李乾德", -"來著" => "来着", -"板著臉" => "板着脸", -"枕著" => "枕着", -"柳詒徵" => "柳诒徵", -"標志著" => "标志着", -"夢著" => "梦着", -"梳著" => "梳着", -"棋高一著" => "棋高一着", -"樊於期" => "樊於期", -"歇著" => "歇着", -"歪打正著" => "歪打正着", -"比畫 " => "比画 ", -"比著" => "比着", -"水鹼" => "水碱", -"水粉畫" => "水粉画", -"求著" => "求着", -"沉著" => "沉着", -"油畫" => "油画", -"沿著" => "沿着", -"洋鹼" => "洋碱", -"活著" => "活着", -"流著" => "流着", -"浮著" => "浮着", -"海景畫" => "海景画", -"塗畫" => "涂画", -"塗著" => "涂着", -"涎著臉" => "涎着脸", -"潤著" => "润着", -"涵著" => "涵着", -"涵著作" => "涵著作", -"涵著名" => "涵著名", -"涵著者" => "涵著者", -"渴著" => "渴着", -"溢著" => "溢着", -"演著" => "演着", -"演著作" => "演著作", -"演著名" => "演著名", -"演著者" => "演著者", -"漫畫" => "漫画", -"漫著" => "漫着", -"潛伏著 " => "潜伏着 ", -"炭畫" => "炭画", -"點畫" => "点画", -"點著" => "点着", -"煙鹼" => "烟碱", -"烤著" => "烤着", -"燒著" => "烧着", -"燒鹼" => "烧碱", -"照著" => "照着", -"照著作" => "照著作", -"照著名" => "照著名", -"照著者" => "照著者", -"愛著" => "爱着", -"愛鬧著玩" => "爱闹着玩", -"版畫" => "版画", -"牽著" => "牵着", -"犯不著" => "犯不着", -"獨著" => "独着", -"猜著" => "猜着", -"猜著 " => "猜着 ", -"玩著" => "玩着", -"甜著" => "甜着", -"用不著" => "用不着", -"用著" => "用着", -"用著作" => "用著作", -"畫 " => "画 ", -"畫一" => "画一", -"畫上" => "画上", -"畫下" => "画下", -"畫中" => "画中", -"畫了" => "画了", -"畫供" => "画供", -"畫像" => "画像", -"畫兒" => "画儿", -"畫具" => "画具", -"畫冊" => "画册", -"畫出" => "画出", -"畫刊" => "画刊", -"畫匠" => "画匠", -"畫卷" => "画卷", -"畫史" => "画史", -"畫品" => "画品", -"畫商" => "画商", -"畫圖" => "画图", -"畫圈" => "画圈", -"畫壇" => "画坛", -"畫境" => "画境", -"畫外" => "画外", -"畫室" => "画室", -"畫家" => "画家", -"畫屏 " => "画屏 ", -"畫展" => "画展", -"畫工" => "画工", -"畫布" => "画布", -"畫師 " => "画师 ", -"畫帖" => "画帖", -"畫幅" => "画幅", -"畫廊" => "画廊", -"畫意" => "画意", -"畫成" => "画成", -"畫報" => "画报", -"畫押" => "画押", -"畫景" => "画景", -"畫本" => "画本", -"畫板 " => "画板 ", -"畫架" => "画架", -"畫框" => "画框", -"畫法" => "画法", -"畫片" => "画片", -"畫王" => "画王", -"畫畫" => "画画", -"畫界" => "画界", -"畫皮" => "画皮", -"畫眉" => "画眉", -"畫稿" => "画稿", -"畫筆" => "画笔", -"畫符" => "画符", -"畫紙" => "画纸", -"畫線" => "画线", -"畫航" => "画航", -"畫舫" => "画舫", -"畫虎" => "画虎", -"畫論" => "画论", -"畫譜" => "画谱", -"畫象" => "画象", -"畫質" => "画质", -"畫貼" => "画贴", -"畫軸" => "画轴", -"畫院" => "画院", -"畫集 " => "画集 ", -"畫面" => "画面", -"畫頁" => "画页", -"留著" => "留着", -"疑著" => "疑着", -"皺著" => "皱着", -"鹽鹼" => "盐碱", -"盛著" => "盛着", -"盯著" => "盯着", -"直著" => "直着", -"盼著" => "盼着", -"盾著" => "盾着", -"看著" => "看着", -"眯著" => "眯着", -"著 " => "着 ", -"著絲" => "着丝", -"著人先鞭" => "着人先鞭", -"著什么急" => "着什么急", -"著他" => "着他", -"著你" => "着你", -"著兒" => "着儿", -"著涼" => "着凉", -"著力" => "着力", -"著呢 " => "着呢 ", -"著地" => "着地", -"著墨" => "着墨", -"著她" => "着她", -"著妳" => "着妳", -"著它" => "着它", -"著實" => "着实", -"著床" => "着床", -"著錄" => "着录", -"著忙" => "着忙", -"著急" => "着急", -"著惱" => "着恼", -"著想" => "着想", -"著意" => "着意", -"著慌" => "着慌", -"著我" => "着我", -"著手" => "着手", -"著數" => "着数", -"著棋 " => "着棋 ", -"著法" => "着法", -"著火" => "着火", -"著眼" => "着眼", -"著祂" => "着祂", -"著筆" => "着笔", -"著緊" => "着紧", -"著腳" => "着脚", -"著艦" => "着舰", -"著色" => "着色", -"著花" => "着花", -"著落" => "着落", -"著衣" => "着衣", -"著裝" => "着装", -"著迷" => "着迷", -"著重" => "着重", -"著陸" => "着陆", -"著鞭" => "着鞭", -"著魔" => "着魔", -"睜著" => "睁着", -"睡不著" => "睡不着", -"睡著" => "睡着", -"瞄著" => "瞄着", -"瞅著" => "瞅着", -"瞞著" => "瞒着", -"瞧著" => "瞧着", -"瞪著" => "瞪着", -"知疼著熱" => "知疼着热", -"硝鹼" => "硝碱", -"硬著" => "硬着", -"鹼 " => "碱 ", -"鹼化" => "碱化", -"鹼場" => "碱场", -"鹼基" => "碱基", -"鹼度" => "碱度", -"鹼性" => "碱性", -"鹼水" => "碱水", -"鹼熔" => "碱熔", -"鹼類" => "碱类", -"磁畫" => "磁画", -"福著" => "福着", -"積著" => "积着", -"空著" => "空着", -"穿著" => "穿着", -"立著" => "立着", -"豎著" => "竖着", -"站著" => "站着", -"端著" => "端着", -"笑著" => "笑着", -"筆畫" => "笔画", -"等著" => "等着", -"策畫" => "策画", -"管著" => "管着", -"粘著" => "粘着", -"系著" => "系着", -"緊著" => "紧着", -"純鹼" => "纯碱", -"組畫" => "组画", -"細密畫" => "细密画", -"綁著" => "绑着", -"繞著" => "绕着", -"繪畫" => "绘画", -"絹畫" => "绢画", -"綳著勁" => "绷着劲", -"綳著臉 " => "绷着脸 ", -"纏著" => "缠着", -"罩著" => "罩着", -"美著" => "美着", -"美著作" => "美著作", -"美著名" => "美著名", -"美著者" => "美著者", -"耀著" => "耀着", -"老著臉皮" => "老着脸皮", -"考著" => "考着", -"耐鹼" => "耐碱", -"聊著" => "聊着", -"肉鹼" => "肉碱", -"肖像畫" => "肖像画", -"背著" => "背着", -"膠畫" => "胶画", -"膠著" => "胶着", -"腆著" => "腆着", -"舞著" => "舞着", -"藝著" => "艺着", -"苦著" => "苦着", -"茶鹼" => "茶碱", -"獲著" => "获着", -"蕭乾" => "萧乾", -"落著" => "落着", -"蒙著" => "蒙着", -"藏著" => "藏着", -"蘸著" => "蘸着", -"行著" => "行着", -"衣著" => "衣着", -"裝著" => "装着", -"裸體畫" => "裸体画", -"裹著" => "裹着", -"西洋畫" => "西洋画", -"西畫" => "西画", -"見著" => "见着", -"覺著" => "觉着", -"記著" => "记着", -"講著" => "讲着", -"試著" => "试着", -"該著" => "该着", -"語著" => "语着", -"說著" => "说着", -"豫著" => "豫着", -"貞著" => "贞着", -"貼畫" => "贴画", -"貼著" => "贴着", -"貿著之仇" => "贸着之仇", -"走著" => "走着", -"趕著" => "赶着", -"起著" => "起着", -"趁著" => "趁着", -"趴著" => "趴着", -"躍著" => "跃着", -"跐著腳 " => "跐着脚 ", -"跑著" => "跑着", -"跟著" => "跟着", -"跪著" => "跪着", -"跳著" => "跳着", -"踏著" => "踏着", -"踩著" => "踩着", -"蹲著 " => "蹲着 ", -"身著" => "身着", -"躺著" => "躺着", -"轉著" => "转着", -"載著" => "载着", -"達著" => "达着", -"過著" => "过着", -"邁著" => "迈着", -"迎著" => "迎着", -"返鹼" => "返碱", -"這么著" => "这么着", -"遠著" => "远着", -"連環畫" => "连环画", -"連著" => "连着", -"連著作" => "连著作", -"連著名" => "连著名", -"連著者" => "连著者", -"追著" => "追着", -"逆著" => "逆着", -"透著" => "透着", -"透視畫" => "透视画", -"逼著" => "逼着", -"遇著" => "遇着", -"那么著" => "那么着", -"郭子乾" => "郭子乾", -"配著" => "配着", -"酸鹼" => "酸碱", -"釀著" => "酿着", -"醒著" => "醒着", -"鋪著" => "铺着", -"鍾 " => "锺 ", -"鍾鍛" => "锺锻", -"鍛鍾" => "锻锺", -"長著" => "长着", -"閉著" => "闭着", -"問著" => "问着", -"閑著" => "闲着", -"鬧著玩兒" => "闹着玩儿", -"附著" => "附着", -"陋著" => "陋着", -"陪著" => "陪着", -"隨著" => "随着", -"隔著" => "隔着", -"雅著" => "雅着", -"雕畫" => "雕画", -"靜物畫" => "静物画", -"靠著" => "靠着", -"面對著 " => "面对着 ", -"頂著" => "顶着", -"順著" => "顺着", -"顧著" => "顾着", -"領著" => "领着", -"風景畫" => "风景画", -"飄著" => "飘着", -"餘 " => "馀 ", -"餘年" => "馀年", -"駕著" => "驾着", -"罵著" => "骂着", -"騎著" => "骑着", -"騙著" => "骗着", -"高著" => "高着", -"髭著" => "髭着", -"魏徵" => "魏徵", -"鯰 " => "鲶 ", -"鯰魚" => "鲶鱼", -"黏著" => "黏着", -"龕著" => "龛着", -"乾县" => "乾县", -"萧乾" => "萧乾", -"乾断" => "乾断", -"乾图" => "乾图", -"乾纲" => "乾纲", -"乾红" => "乾红", -"乾清宫" => "乾清宫", -"柳诒徵" => "柳诒徵", -"於夫罗" => "於夫罗", -"於梨华" => "於梨华", -"于潜县" => "於潜县", -"憑藉" => "凭借", -"藉端" => "借端", -"藉故" => "借故", -"藉口" => "借口", -"藉助" => "借助", -"藉手" => "借手", -"藉詞" => "借词", -"藉機" => "借机", -"藉此" => "借此", -"藉由" => "借由", +'㑳' => '㑇', +'㠏' => '㟆', +'㩜' => '㨫', +'䊷' => '䌶', +'䋙' => '䌺', +'䋻' => '䌾', +'䌈' => '𦈖', +'䝼' => '䞍', +'䪏' => '𩏼', +'䪗' => '𩐀', +'䪘' => '𩏿', +'䫴' => '𩖗', +'䬘' => '𩙮', +'䬝' => '𩙯', +'䭀' => '𩠇', +'䭃' => '𩠈', +'䭿' => '𩧭', +'䮝' => '𩧰', +'䮞' => '𩨁', +'䮠' => '𩧿', +'䮳' => '𩨏', +'䮾' => '𩧪', +'䯀' => '䯅', +'䰾' => '鲃', +'䱙' => '𩾈', +'䱬' => '𩾊', +'䱰' => '𩾋', +'䱷' => '䲣', +'䱽' => '䲝', +'䲁' => '鳚', +'䲰' => '𪉂', +'䴬' => '𪎈', +'䴴' => '𪎋', +'丟' => '丢', +'並' => '并', +'乾' => '干', +'亂' => '乱', +'亙' => '亘', +'亞' => '亚', +'佇' => '伫', +'佈' => '布', +'佔' => '占', +'併' => '并', +'來' => '来', +'侖' => '仑', +'侶' => '侣', +'俁' => '俣', +'係' => '系', +'俔' => '伣', +'俠' => '侠', +'倀' => '伥', +'倆' => '俩', +'倈' => '俫', +'倉' => '仓', +'個' => '个', +'們' => '们', +'倖' => '幸', +'倫' => '伦', +'偉' => '伟', +'側' => '侧', +'偵' => '侦', +'偽' => '伪', +'傑' => '杰', +'傖' => '伧', +'傘' => '伞', +'備' => '备', +'傢' => '家', +'傭' => '佣', +'傯' => '偬', +'傳' => '传', +'傴' => '伛', +'債' => '债', +'傷' => '伤', +'傾' => '倾', +'僂' => '偻', +'僅' => '仅', +'僉' => '佥', +'僑' => '侨', +'僕' => '仆', +'僞' => '伪', +'僥' => '侥', +'僨' => '偾', +'僱' => '雇', +'價' => '价', +'儀' => '仪', +'儂' => '侬', +'億' => '亿', +'儈' => '侩', +'儉' => '俭', +'儐' => '傧', +'儔' => '俦', +'儕' => '侪', +'儘' => '尽', +'償' => '偿', +'優' => '优', +'儲' => '储', +'儷' => '俪', +'儸' => '㑩', +'儺' => '傩', +'儻' => '傥', +'儼' => '俨', +'兇' => '凶', +'兌' => '兑', +'兒' => '儿', +'兗' => '兖', +'內' => '内', +'兩' => '两', +'冊' => '册', +'冪' => '幂', +'凈' => '净', +'凍' => '冻', +'凜' => '凛', +'凱' => '凯', +'別' => '别', +'刪' => '删', +'剄' => '刭', +'則' => '则', +'剋' => '克', +'剎' => '刹', +'剗' => '刬', +'剛' => '刚', +'剝' => '剥', +'剮' => '剐', +'剴' => '剀', +'創' => '创', +'剷' => '铲', +'劃' => '划', +'劇' => '剧', +'劉' => '刘', +'劊' => '刽', +'劌' => '刿', +'劍' => '剑', +'劏' => '㓥', +'劑' => '剂', +'劚' => '㔉', +'勁' => '劲', +'動' => '动', +'務' => '务', +'勛' => '勋', +'勝' => '胜', +'勞' => '劳', +'勢' => '势', +'勩' => '勚', +'勱' => '劢', +'勳' => '勋', +'勵' => '励', +'勸' => '劝', +'勻' => '匀', +'匭' => '匦', +'匯' => '汇', +'匱' => '匮', +'區' => '区', +'協' => '协', +'卻' => '却', +'卽' => '即', +'厙' => '厍', +'厠' => '厕', +'厤' => '历', +'厭' => '厌', +'厲' => '厉', +'厴' => '厣', +'參' => '参', +'叄' => '叁', +'叢' => '丛', +'吒' => '咤', +'吳' => '吴', +'吶' => '呐', +'呂' => '吕', +'咼' => '呙', +'員' => '员', +'唄' => '呗', +'唚' => '吣', +'問' => '问', +'啓' => '启', +'啞' => '哑', +'啟' => '启', +'啢' => '唡', +'喎' => '㖞', +'喚' => '唤', +'喪' => '丧', +'喫' => '吃', +'喬' => '乔', +'單' => '单', +'喲' => '哟', +'嗆' => '呛', +'嗇' => '啬', +'嗊' => '唝', +'嗎' => '吗', +'嗚' => '呜', +'嗩' => '唢', +'嗰' => '𠮶', +'嗶' => '哔', +'嘆' => '叹', +'嘍' => '喽', +'嘔' => '呕', +'嘖' => '啧', +'嘗' => '尝', +'嘜' => '唛', +'嘩' => '哗', +'嘮' => '唠', +'嘯' => '啸', +'嘰' => '叽', +'嘵' => '哓', +'嘸' => '呒', +'嘽' => '啴', +'噁' => '恶', +'噓' => '嘘', +'噚' => '㖊', +'噝' => '咝', +'噠' => '哒', +'噥' => '哝', +'噦' => '哕', +'噯' => '嗳', +'噲' => '哙', +'噴' => '喷', +'噸' => '吨', +'噹' => '当', +'嚀' => '咛', +'嚇' => '吓', +'嚌' => '哜', +'嚐' => '尝', +'嚕' => '噜', +'嚙' => '啮', +'嚥' => '咽', +'嚦' => '呖', +'嚨' => '咙', +'嚮' => '向', +'嚲' => '亸', +'嚳' => '喾', +'嚴' => '严', +'嚶' => '嘤', +'囀' => '啭', +'囁' => '嗫', +'囂' => '嚣', +'囅' => '冁', +'囈' => '呓', +'囌' => '苏', +'囑' => '嘱', +'囪' => '囱', +'圇' => '囵', +'國' => '国', +'圍' => '围', +'園' => '园', +'圓' => '圆', +'圖' => '图', +'團' => '团', +'垵' => '埯', +'埡' => '垭', +'埰' => '采', +'執' => '执', +'堅' => '坚', +'堊' => '垩', +'堖' => '垴', +'堝' => '埚', +'堯' => '尧', +'報' => '报', +'場' => '场', +'塊' => '块', +'塋' => '茔', +'塏' => '垲', +'塒' => '埘', +'塗' => '涂', +'塚' => '冢', +'塢' => '坞', +'塤' => '埙', +'塵' => '尘', +'塹' => '堑', +'墊' => '垫', +'墜' => '坠', +'墮' => '堕', +'墰' => '坛', +'墳' => '坟', +'墻' => '墙', +'墾' => '垦', +'壇' => '坛', +'壈' => '𡒄', +'壋' => '垱', +'壓' => '压', +'壘' => '垒', +'壙' => '圹', +'壚' => '垆', +'壜' => '坛', +'壞' => '坏', +'壟' => '垄', +'壠' => '垅', +'壢' => '坜', +'壩' => '坝', +'壯' => '壮', +'壺' => '壶', +'壼' => '壸', +'壽' => '寿', +'夠' => '够', +'夢' => '梦', +'夥' => '伙', +'夾' => '夹', +'奐' => '奂', +'奧' => '奥', +'奩' => '奁', +'奪' => '夺', +'奬' => '奖', +'奮' => '奋', +'奼' => '姹', +'妝' => '妆', +'姍' => '姗', +'姦' => '奸', +'娛' => '娱', +'婁' => '娄', +'婦' => '妇', +'婭' => '娅', +'媧' => '娲', +'媯' => '妫', +'媼' => '媪', +'媽' => '妈', +'嫗' => '妪', +'嫵' => '妩', +'嫻' => '娴', +'嫿' => '婳', +'嬀' => '妫', +'嬈' => '娆', +'嬋' => '婵', +'嬌' => '娇', +'嬙' => '嫱', +'嬡' => '嫒', +'嬤' => '嬷', +'嬪' => '嫔', +'嬰' => '婴', +'嬸' => '婶', +'孌' => '娈', +'孫' => '孙', +'學' => '学', +'孿' => '孪', +'宮' => '宫', +'寀' => '采', +'寢' => '寝', +'實' => '实', +'寧' => '宁', +'審' => '审', +'寫' => '写', +'寬' => '宽', +'寵' => '宠', +'寶' => '宝', +'將' => '将', +'專' => '专', +'尋' => '寻', +'對' => '对', +'導' => '导', +'尷' => '尴', +'屆' => '届', +'屍' => '尸', +'屓' => '屃', +'屜' => '屉', +'屢' => '屡', +'層' => '层', +'屨' => '屦', +'屬' => '属', +'岡' => '冈', +'峴' => '岘', +'島' => '岛', +'峽' => '峡', +'崍' => '崃', +'崑' => '昆', +'崗' => '岗', +'崙' => '仑', +'崢' => '峥', +'崬' => '岽', +'嵐' => '岚', +'嵗' => '岁', +'嶁' => '嵝', +'嶄' => '崭', +'嶇' => '岖', +'嶔' => '嵚', +'嶗' => '崂', +'嶠' => '峤', +'嶢' => '峣', +'嶧' => '峄', +'嶮' => '崄', +'嶴' => '岙', +'嶸' => '嵘', +'嶺' => '岭', +'嶼' => '屿', +'嶽' => '岳', +'巋' => '岿', +'巒' => '峦', +'巔' => '巅', +'巰' => '巯', +'帥' => '帅', +'師' => '师', +'帳' => '帐', +'帶' => '带', +'幀' => '帧', +'幃' => '帏', +'幗' => '帼', +'幘' => '帻', +'幟' => '帜', +'幣' => '币', +'幫' => '帮', +'幬' => '帱', +'幹' => '干', +'幺' => '么', +'幾' => '几', +'庫' => '库', +'廁' => '厕', +'廂' => '厢', +'廄' => '厩', +'廈' => '厦', +'廚' => '厨', +'廝' => '厮', +'廟' => '庙', +'廠' => '厂', +'廡' => '庑', +'廢' => '废', +'廣' => '广', +'廩' => '廪', +'廬' => '庐', +'廳' => '厅', +'弒' => '弑', +'弔' => '吊', +'弳' => '弪', +'張' => '张', +'強' => '强', +'彆' => '别', +'彈' => '弹', +'彌' => '弥', +'彎' => '弯', +'彙' => '汇', +'彞' => '彝', +'彥' => '彦', +'後' => '后', +'徑' => '径', +'從' => '从', +'徠' => '徕', +'復' => '复', +'徵' => '征', +'徹' => '彻', +'恆' => '恒', +'恥' => '耻', +'悅' => '悦', +'悞' => '悮', +'悵' => '怅', +'悶' => '闷', +'惡' => '恶', +'惱' => '恼', +'惲' => '恽', +'惻' => '恻', +'愛' => '爱', +'愜' => '惬', +'愨' => '悫', +'愴' => '怆', +'愷' => '恺', +'愾' => '忾', +'慄' => '栗', +'態' => '态', +'慍' => '愠', +'慘' => '惨', +'慚' => '惭', +'慟' => '恸', +'慣' => '惯', +'慤' => '悫', +'慪' => '怄', +'慫' => '怂', +'慮' => '虑', +'慳' => '悭', +'慶' => '庆', +'慼' => '戚', +'慾' => '欲', +'憂' => '忧', +'憊' => '惫', +'憐' => '怜', +'憑' => '凭', +'憒' => '愦', +'憚' => '惮', +'憤' => '愤', +'憫' => '悯', +'憮' => '怃', +'憲' => '宪', +'憶' => '忆', +'懇' => '恳', +'應' => '应', +'懌' => '怿', +'懍' => '懔', +'懞' => '蒙', +'懟' => '怼', +'懣' => '懑', +'懨' => '恹', +'懲' => '惩', +'懶' => '懒', +'懷' => '怀', +'懸' => '悬', +'懺' => '忏', +'懼' => '惧', +'懾' => '慑', +'戀' => '恋', +'戇' => '戆', +'戔' => '戋', +'戧' => '戗', +'戩' => '戬', +'戰' => '战', +'戱' => '戯', +'戲' => '戏', +'戶' => '户', +'拋' => '抛', +'拚' => '拼', +'挩' => '捝', +'挱' => '挲', +'挾' => '挟', +'捨' => '舍', +'捫' => '扪', +'捱' => '挨', +'捲' => '卷', +'掃' => '扫', +'掄' => '抡', +'掗' => '挜', +'掙' => '挣', +'掛' => '挂', +'採' => '采', +'揀' => '拣', +'揚' => '扬', +'換' => '换', +'揮' => '挥', +'損' => '损', +'搖' => '摇', +'搗' => '捣', +'搵' => '揾', +'搶' => '抢', +'摑' => '掴', +'摜' => '掼', +'摟' => '搂', +'摯' => '挚', +'摳' => '抠', +'摶' => '抟', +'摺' => '折', +'摻' => '掺', +'撈' => '捞', +'撏' => '挦', +'撐' => '撑', +'撓' => '挠', +'撝' => '㧑', +'撟' => '挢', +'撣' => '掸', +'撥' => '拨', +'撫' => '抚', +'撲' => '扑', +'撳' => '揿', +'撻' => '挞', +'撾' => '挝', +'撿' => '捡', +'擁' => '拥', +'擄' => '掳', +'擇' => '择', +'擊' => '击', +'擋' => '挡', +'擓' => '㧟', +'擔' => '担', +'據' => '据', +'擠' => '挤', +'擬' => '拟', +'擯' => '摈', +'擰' => '拧', +'擱' => '搁', +'擲' => '掷', +'擴' => '扩', +'擷' => '撷', +'擺' => '摆', +'擻' => '擞', +'擼' => '撸', +'擾' => '扰', +'攄' => '摅', +'攆' => '撵', +'攏' => '拢', +'攔' => '拦', +'攖' => '撄', +'攙' => '搀', +'攛' => '撺', +'攜' => '携', +'攝' => '摄', +'攢' => '攒', +'攣' => '挛', +'攤' => '摊', +'攪' => '搅', +'攬' => '揽', +'敗' => '败', +'敘' => '叙', +'敵' => '敌', +'數' => '数', +'斂' => '敛', +'斃' => '毙', +'斕' => '斓', +'斬' => '斩', +'斷' => '断', +'於' => '于', +'旂' => '旗', +'旣' => '既', +'昇' => '升', +'時' => '时', +'晉' => '晋', +'晝' => '昼', +'暈' => '晕', +'暉' => '晖', +'暘' => '旸', +'暢' => '畅', +'暫' => '暂', +'曄' => '晔', +'曆' => '历', +'曇' => '昙', +'曉' => '晓', +'曏' => '向', +'曖' => '暧', +'曠' => '旷', +'曨' => '昽', +'曬' => '晒', +'書' => '书', +'會' => '会', +'朧' => '胧', +'朮' => '术', +'東' => '东', +'杴' => '锨', +'柵' => '栅', +'桿' => '杆', +'梔' => '栀', +'梘' => '枧', +'條' => '条', +'梟' => '枭', +'梲' => '棁', +'棄' => '弃', +'棊' => '棋', +'棖' => '枨', +'棗' => '枣', +'棟' => '栋', +'棡' => '', +'棧' => '栈', +'棲' => '栖', +'棶' => '梾', +'椏' => '桠', +'楊' => '杨', +'楓' => '枫', +'楨' => '桢', +'業' => '业', +'極' => '极', +'榦' => '干', +'榪' => '杩', +'榮' => '荣', +'榲' => '榅', +'榿' => '桤', +'構' => '构', +'槍' => '枪', +'槓' => '杠', +'槤' => '梿', +'槧' => '椠', +'槨' => '椁', +'槳' => '桨', +'樁' => '桩', +'樂' => '乐', +'樅' => '枞', +'樑' => '梁', +'樓' => '楼', +'標' => '标', +'樞' => '枢', +'樣' => '样', +'樸' => '朴', +'樹' => '树', +'樺' => '桦', +'橈' => '桡', +'橋' => '桥', +'機' => '机', +'橢' => '椭', +'橫' => '横', +'檁' => '檩', +'檉' => '柽', +'檔' => '档', +'檜' => '桧', +'檟' => '槚', +'檢' => '检', +'檣' => '樯', +'檮' => '梼', +'檯' => '台', +'檳' => '槟', +'檸' => '柠', +'檻' => '槛', +'櫃' => '柜', +'櫓' => '橹', +'櫚' => '榈', +'櫛' => '栉', +'櫝' => '椟', +'櫞' => '橼', +'櫟' => '栎', +'櫥' => '橱', +'櫧' => '槠', +'櫨' => '栌', +'櫪' => '枥', +'櫫' => '橥', +'櫬' => '榇', +'櫱' => '蘖', +'櫳' => '栊', +'櫸' => '榉', +'櫻' => '樱', +'欄' => '栏', +'欅' => '榉', +'權' => '权', +'欏' => '椤', +'欒' => '栾', +'欖' => '榄', +'欞' => '棂', +'欽' => '钦', +'歎' => '叹', +'歐' => '欧', +'歟' => '欤', +'歡' => '欢', +'歲' => '岁', +'歷' => '历', +'歸' => '归', +'歿' => '殁', +'殘' => '残', +'殞' => '殒', +'殤' => '殇', +'殨' => '㱮', +'殫' => '殚', +'殭' => '僵', +'殮' => '殓', +'殯' => '殡', +'殰' => '㱩', +'殲' => '歼', +'殺' => '杀', +'殻' => '壳', +'殼' => '壳', +'毀' => '毁', +'毆' => '殴', +'毿' => '毵', +'氂' => '牦', +'氈' => '毡', +'氌' => '氇', +'氣' => '气', +'氫' => '氢', +'氬' => '氩', +'氳' => '氲', +'汙' => '污', +'決' => '决', +'沒' => '没', +'沖' => '冲', +'況' => '况', +'泝' => '溯', +'洩' => '泄', +'洶' => '汹', +'浹' => '浃', +'涇' => '泾', +'涼' => '凉', +'淒' => '凄', +'淚' => '泪', +'淥' => '渌', +'淨' => '净', +'淩' => '凌', +'淪' => '沦', +'淵' => '渊', +'淶' => '涞', +'淺' => '浅', +'渙' => '涣', +'減' => '减', +'渦' => '涡', +'測' => '测', +'渾' => '浑', +'湊' => '凑', +'湞' => '浈', +'湧' => '涌', +'湯' => '汤', +'溈' => '沩', +'準' => '准', +'溝' => '沟', +'溫' => '温', +'滄' => '沧', +'滅' => '灭', +'滌' => '涤', +'滎' => '荥', +'滙' => '汇', +'滬' => '沪', +'滯' => '滞', +'滲' => '渗', +'滷' => '卤', +'滸' => '浒', +'滻' => '浐', +'滾' => '滚', +'滿' => '满', +'漁' => '渔', +'漚' => '沤', +'漢' => '汉', +'漣' => '涟', +'漬' => '渍', +'漲' => '涨', +'漵' => '溆', +'漸' => '渐', +'漿' => '浆', +'潁' => '颍', +'潑' => '泼', +'潔' => '洁', +'潙' => '沩', +'潛' => '潜', +'潤' => '润', +'潯' => '浔', +'潰' => '溃', +'潷' => '滗', +'潿' => '涠', +'澀' => '涩', +'澆' => '浇', +'澇' => '涝', +'澐' => '沄', +'澗' => '涧', +'澠' => '渑', +'澤' => '泽', +'澦' => '滪', +'澩' => '泶', +'澮' => '浍', +'澱' => '淀', +'澾' => '㳠', +'濁' => '浊', +'濃' => '浓', +'濕' => '湿', +'濘' => '泞', +'濟' => '济', +'濤' => '涛', +'濫' => '滥', +'濰' => '潍', +'濱' => '滨', +'濺' => '溅', +'濼' => '泺', +'濾' => '滤', +'瀅' => '滢', +'瀆' => '渎', +'瀇' => '㲿', +'瀉' => '泻', +'瀋' => '沈', +'瀏' => '浏', +'瀕' => '濒', +'瀘' => '泸', +'瀝' => '沥', +'瀟' => '潇', +'瀠' => '潆', +'瀦' => '潴', +'瀧' => '泷', +'瀨' => '濑', +'瀰' => '弥', +'瀲' => '潋', +'瀾' => '澜', +'灃' => '沣', +'灄' => '滠', +'灑' => '洒', +'灕' => '漓', +'灘' => '滩', +'灝' => '灏', +'灠' => '漤', +'灣' => '湾', +'灤' => '滦', +'灧' => '滟', +'災' => '灾', +'為' => '为', +'烏' => '乌', +'烴' => '烃', +'無' => '无', +'煉' => '炼', +'煒' => '炜', +'煙' => '烟', +'煢' => '茕', +'煥' => '焕', +'煩' => '烦', +'煬' => '炀', +'煱' => '㶽', +'熅' => '煴', +'熒' => '荧', +'熗' => '炝', +'熱' => '热', +'熲' => '颎', +'熾' => '炽', +'燁' => '烨', +'燈' => '灯', +'燉' => '炖', +'燒' => '烧', +'燙' => '烫', +'燜' => '焖', +'營' => '营', +'燦' => '灿', +'燬' => '毁', +'燭' => '烛', +'燴' => '烩', +'燶' => '㶶', +'燼' => '烬', +'燾' => '焘', +'爍' => '烁', +'爐' => '炉', +'爛' => '烂', +'爭' => '争', +'爲' => '为', +'爺' => '爷', +'爾' => '尔', +'牆' => '墙', +'牘' => '牍', +'牽' => '牵', +'犖' => '荦', +'犢' => '犊', +'犧' => '牺', +'狀' => '状', +'狹' => '狭', +'狽' => '狈', +'猙' => '狰', +'猶' => '犹', +'猻' => '狲', +'獁' => '犸', +'獃' => '呆', +'獄' => '狱', +'獅' => '狮', +'獎' => '奖', +'獨' => '独', +'獪' => '狯', +'獫' => '猃', +'獮' => '狝', +'獰' => '狞', +'獱' => '㺍', +'獲' => '获', +'獵' => '猎', +'獷' => '犷', +'獸' => '兽', +'獺' => '獭', +'獻' => '献', +'獼' => '猕', +'玀' => '猡', +'現' => '现', +'琺' => '珐', +'琿' => '珲', +'瑋' => '玮', +'瑒' => '玚', +'瑣' => '琐', +'瑤' => '瑶', +'瑩' => '莹', +'瑪' => '玛', +'瑲' => '玱', +'璉' => '琏', +'璣' => '玑', +'璦' => '瑷', +'璫' => '珰', +'環' => '环', +'璽' => '玺', +'瓊' => '琼', +'瓏' => '珑', +'瓔' => '璎', +'瓚' => '瓒', +'甌' => '瓯', +'甕' => '瓮', +'產' => '产', +'産' => '产', +'甦' => '苏', +'畝' => '亩', +'畢' => '毕', +'畫' => '画', +'異' => '异', +'畵' => '画', +'當' => '当', +'疇' => '畴', +'疊' => '叠', +'痙' => '痉', +'痠' => '酸', +'痾' => '疴', +'瘂' => '痖', +'瘋' => '疯', +'瘍' => '疡', +'瘓' => '痪', +'瘞' => '瘗', +'瘡' => '疮', +'瘧' => '疟', +'瘮' => '瘆', +'瘲' => '疭', +'瘺' => '瘘', +'瘻' => '瘘', +'療' => '疗', +'癆' => '痨', +'癇' => '痫', +'癉' => '瘅', +'癒' => '愈', +'癘' => '疠', +'癟' => '瘪', +'癡' => '痴', +'癢' => '痒', +'癤' => '疖', +'癥' => '症', +'癧' => '疬', +'癩' => '癞', +'癬' => '癣', +'癭' => '瘿', +'癮' => '瘾', +'癰' => '痈', +'癱' => '瘫', +'癲' => '癫', +'發' => '发', +'皚' => '皑', +'皰' => '疱', +'皸' => '皲', +'皺' => '皱', +'盃' => '杯', +'盜' => '盗', +'盞' => '盏', +'盡' => '尽', +'監' => '监', +'盤' => '盘', +'盧' => '卢', +'盪' => '荡', +'眥' => '眦', +'眾' => '众', +'睏' => '困', +'睜' => '睁', +'睞' => '睐', +'瞘' => '眍', +'瞜' => '䁖', +'瞞' => '瞒', +'瞭' => '了', +'瞶' => '瞆', +'瞼' => '睑', +'矇' => '蒙', +'矓' => '眬', +'矚' => '瞩', +'矯' => '矫', +'硃' => '朱', +'硜' => '硁', +'硤' => '硖', +'硨' => '砗', +'硯' => '砚', +'碕' => '埼', +'碩' => '硕', +'碭' => '砀', +'碸' => '砜', +'確' => '确', +'碼' => '码', +'磑' => '硙', +'磚' => '砖', +'磣' => '碜', +'磧' => '碛', +'磯' => '矶', +'磽' => '硗', +'礆' => '硷', +'礎' => '础', +'礙' => '碍', +'礦' => '矿', +'礪' => '砺', +'礫' => '砾', +'礬' => '矾', +'礱' => '砻', +'祘' => '算', +'祿' => '禄', +'禍' => '祸', +'禎' => '祯', +'禕' => '祎', +'禡' => '祃', +'禦' => '御', +'禪' => '禅', +'禮' => '礼', +'禰' => '祢', +'禱' => '祷', +'禿' => '秃', +'秈' => '籼', +'稅' => '税', +'稈' => '秆', +'稏' => '䅉', +'稜' => '棱', +'稟' => '禀', +'種' => '种', +'稱' => '称', +'穀' => '谷', +'穌' => '稣', +'積' => '积', +'穎' => '颖', +'穠' => '秾', +'穡' => '穑', +'穢' => '秽', +'穩' => '稳', +'穫' => '获', +'穭' => '稆', +'窩' => '窝', +'窪' => '洼', +'窮' => '穷', +'窯' => '窑', +'窵' => '窎', +'窶' => '窭', +'窺' => '窥', +'竄' => '窜', +'竅' => '窍', +'竇' => '窦', +'竈' => '灶', +'竊' => '窃', +'竪' => '竖', +'競' => '竞', +'筆' => '笔', +'筍' => '笋', +'筧' => '笕', +'筴' => '䇲', +'箇' => '个', +'箋' => '笺', +'箏' => '筝', +'節' => '节', +'範' => '范', +'築' => '筑', +'篋' => '箧', +'篔' => '筼', +'篤' => '笃', +'篩' => '筛', +'篳' => '筚', +'簀' => '箦', +'簍' => '篓', +'簑' => '蓑', +'簞' => '箪', +'簡' => '简', +'簣' => '篑', +'簫' => '箫', +'簹' => '筜', +'簽' => '签', +'簾' => '帘', +'籃' => '篮', +'籌' => '筹', +'籙' => '箓', +'籜' => '箨', +'籟' => '籁', +'籠' => '笼', +'籤' => '签', +'籩' => '笾', +'籪' => '簖', +'籬' => '篱', +'籮' => '箩', +'籲' => '吁', +'粵' => '粤', +'糝' => '糁', +'糞' => '粪', +'糧' => '粮', +'糰' => '团', +'糲' => '粝', +'糴' => '籴', +'糶' => '粜', +'糹' => '纟', +'糾' => '纠', +'紀' => '纪', +'紂' => '纣', +'約' => '约', +'紅' => '红', +'紆' => '纡', +'紇' => '纥', +'紈' => '纨', +'紉' => '纫', +'紋' => '纹', +'納' => '纳', +'紐' => '纽', +'紓' => '纾', +'純' => '纯', +'紕' => '纰', +'紖' => '纼', +'紗' => '纱', +'紘' => '纮', +'紙' => '纸', +'級' => '级', +'紛' => '纷', +'紜' => '纭', +'紝' => '纴', +'紡' => '纺', +'紬' => '䌷', +'紮' => '扎', +'細' => '细', +'紱' => '绂', +'紲' => '绁', +'紳' => '绅', +'紵' => '纻', +'紹' => '绍', +'紺' => '绀', +'紼' => '绋', +'紿' => '绐', +'絀' => '绌', +'終' => '终', +'組' => '组', +'絅' => '䌹', +'絆' => '绊', +'絎' => '绗', +'結' => '结', +'絕' => '绝', +'絛' => '绦', +'絝' => '绔', +'絞' => '绞', +'絡' => '络', +'絢' => '绚', +'給' => '给', +'絨' => '绒', +'絰' => '绖', +'統' => '统', +'絲' => '丝', +'絳' => '绛', +'絶' => '绝', +'絹' => '绢', +'綁' => '绑', +'綃' => '绡', +'綆' => '绠', +'綈' => '绨', +'綉' => '绣', +'綌' => '绤', +'綏' => '绥', +'綐' => '䌼', +'經' => '经', +'綜' => '综', +'綞' => '缍', +'綠' => '绿', +'綢' => '绸', +'綣' => '绻', +'綫' => '线', +'綬' => '绶', +'維' => '维', +'綯' => '绹', +'綰' => '绾', +'綱' => '纲', +'網' => '网', +'綳' => '绷', +'綴' => '缀', +'綵' => '彩', +'綸' => '纶', +'綹' => '绺', +'綺' => '绮', +'綻' => '绽', +'綽' => '绰', +'綾' => '绫', +'綿' => '绵', +'緄' => '绲', +'緇' => '缁', +'緊' => '紧', +'緋' => '绯', +'緑' => '绿', +'緒' => '绪', +'緓' => '绬', +'緔' => '绱', +'緗' => '缃', +'緘' => '缄', +'緙' => '缂', +'線' => '线', +'緝' => '缉', +'緞' => '缎', +'締' => '缔', +'緡' => '缗', +'緣' => '缘', +'緦' => '缌', +'編' => '编', +'緩' => '缓', +'緬' => '缅', +'緯' => '纬', +'緱' => '缑', +'緲' => '缈', +'練' => '练', +'緶' => '缏', +'緹' => '缇', +'緻' => '致', +'縈' => '萦', +'縉' => '缙', +'縊' => '缢', +'縋' => '缒', +'縐' => '绉', +'縑' => '缣', +'縕' => '缊', +'縗' => '缞', +'縛' => '缚', +'縝' => '缜', +'縞' => '缟', +'縟' => '缛', +'縣' => '县', +'縧' => '绦', +'縫' => '缝', +'縭' => '缡', +'縮' => '缩', +'縱' => '纵', +'縲' => '缧', +'縳' => '䌸', +'縴' => '纤', +'縵' => '缦', +'縶' => '絷', +'縷' => '缕', +'縹' => '缥', +'總' => '总', +'績' => '绩', +'繃' => '绷', +'繅' => '缫', +'繆' => '缪', +'繐' => '穗', +'繒' => '缯', +'織' => '织', +'繕' => '缮', +'繚' => '缭', +'繞' => '绕', +'繡' => '绣', +'繢' => '缋', +'繩' => '绳', +'繪' => '绘', +'繫' => '系', +'繭' => '茧', +'繮' => '缰', +'繯' => '缳', +'繰' => '缲', +'繳' => '缴', +'繸' => '䍁', +'繹' => '绎', +'繼' => '继', +'繽' => '缤', +'繾' => '缱', +'繿' => '䍀', +'纈' => '缬', +'纊' => '纩', +'續' => '续', +'纍' => '累', +'纏' => '缠', +'纓' => '缨', +'纔' => '才', +'纖' => '纤', +'纘' => '缵', +'纜' => '缆', +'缽' => '钵', +'罈' => '坛', +'罌' => '罂', +'罎' => '坛', +'罰' => '罚', +'罵' => '骂', +'罷' => '罢', +'羅' => '罗', +'羆' => '罴', +'羈' => '羁', +'羋' => '芈', +'羥' => '羟', +'義' => '义', +'習' => '习', +'翹' => '翘', +'耬' => '耧', +'耮' => '耢', +'聖' => '圣', +'聞' => '闻', +'聯' => '联', +'聰' => '聪', +'聲' => '声', +'聳' => '耸', +'聵' => '聩', +'聶' => '聂', +'職' => '职', +'聹' => '聍', +'聽' => '听', +'聾' => '聋', +'肅' => '肃', +'脅' => '胁', +'脈' => '脉', +'脛' => '胫', +'脣' => '唇', +'脫' => '脱', +'脹' => '胀', +'腎' => '肾', +'腖' => '胨', +'腡' => '脶', +'腦' => '脑', +'腫' => '肿', +'腳' => '脚', +'腸' => '肠', +'膃' => '腽', +'膚' => '肤', +'膠' => '胶', +'膩' => '腻', +'膽' => '胆', +'膾' => '脍', +'膿' => '脓', +'臉' => '脸', +'臍' => '脐', +'臏' => '膑', +'臘' => '腊', +'臚' => '胪', +'臟' => '脏', +'臠' => '脔', +'臢' => '臜', +'臥' => '卧', +'臨' => '临', +'臺' => '台', +'與' => '与', +'興' => '兴', +'舉' => '举', +'舊' => '旧', +'艙' => '舱', +'艤' => '舣', +'艦' => '舰', +'艫' => '舻', +'艱' => '艰', +'艷' => '艳', +'芻' => '刍', +'苧' => '苎', +'茲' => '兹', +'荊' => '荆', +'莊' => '庄', +'莖' => '茎', +'莢' => '荚', +'莧' => '苋', +'華' => '华', +'菴' => '庵', +'萇' => '苌', +'萊' => '莱', +'萬' => '万', +'萵' => '莴', +'葉' => '叶', +'葒' => '荭', +'葤' => '荮', +'葦' => '苇', +'葯' => '药', +'葷' => '荤', +'蒓' => '莼', +'蒔' => '莳', +'蒞' => '莅', +'蒼' => '苍', +'蓀' => '荪', +'蓋' => '盖', +'蓮' => '莲', +'蓯' => '苁', +'蓴' => '莼', +'蓽' => '荜', +'蔔' => '卜', +'蔘' => '参', +'蔞' => '蒌', +'蔣' => '蒋', +'蔥' => '葱', +'蔦' => '茑', +'蔭' => '荫', +'蕁' => '荨', +'蕆' => '蒇', +'蕎' => '荞', +'蕒' => '荬', +'蕓' => '芸', +'蕕' => '莸', +'蕘' => '荛', +'蕢' => '蒉', +'蕩' => '荡', +'蕪' => '芜', +'蕭' => '萧', +'蕷' => '蓣', +'薀' => '蕰', +'薈' => '荟', +'薊' => '蓟', +'薌' => '芗', +'薑' => '姜', +'薔' => '蔷', +'薘' => '荙', +'薟' => '莶', +'薦' => '荐', +'薩' => '萨', +'薳' => '䓕', +'薴' => '苧', +'薺' => '荠', +'藍' => '蓝', +'藎' => '荩', +'藝' => '艺', +'藥' => '药', +'藪' => '薮', +'藴' => '蕴', +'藶' => '苈', +'藹' => '蔼', +'藺' => '蔺', +'蘄' => '蕲', +'蘆' => '芦', +'蘇' => '苏', +'蘊' => '蕴', +'蘋' => '苹', +'蘚' => '藓', +'蘞' => '蔹', +'蘢' => '茏', +'蘭' => '兰', +'蘺' => '蓠', +'蘿' => '萝', +'虆' => '蔂', +'處' => '处', +'虛' => '虚', +'虜' => '虏', +'號' => '号', +'虧' => '亏', +'虯' => '虬', +'蛺' => '蛱', +'蛻' => '蜕', +'蜆' => '蚬', +'蝕' => '蚀', +'蝟' => '猬', +'蝦' => '虾', +'蝸' => '蜗', +'螄' => '蛳', +'螞' => '蚂', +'螢' => '萤', +'螮' => '䗖', +'螻' => '蝼', +'螿' => '螀', +'蟄' => '蛰', +'蟈' => '蝈', +'蟎' => '螨', +'蟣' => '虮', +'蟬' => '蝉', +'蟯' => '蛲', +'蟲' => '虫', +'蟶' => '蛏', +'蟻' => '蚁', +'蠅' => '蝇', +'蠆' => '虿', +'蠐' => '蛴', +'蠑' => '蝾', +'蠟' => '蜡', +'蠣' => '蛎', +'蠨' => '蟏', +'蠱' => '蛊', +'蠶' => '蚕', +'蠻' => '蛮', +'衆' => '众', +'衊' => '蔑', +'術' => '术', +'衕' => '同', +'衚' => '胡', +'衛' => '卫', +'衝' => '冲', +'衹' => '只', +'袞' => '衮', +'裊' => '袅', +'裏' => '里', +'補' => '补', +'裝' => '装', +'裡' => '里', +'製' => '制', +'複' => '复', +'褌' => '裈', +'褘' => '袆', +'褲' => '裤', +'褳' => '裢', +'褸' => '褛', +'褻' => '亵', +'襇' => '裥', +'襏' => '袯', +'襖' => '袄', +'襝' => '裣', +'襠' => '裆', +'襤' => '褴', +'襪' => '袜', +'襬' => '䙓', +'襯' => '衬', +'襲' => '袭', +'見' => '见', +'覎' => '觃', +'規' => '规', +'覓' => '觅', +'視' => '视', +'覘' => '觇', +'覡' => '觋', +'覥' => '觍', +'覦' => '觎', +'親' => '亲', +'覬' => '觊', +'覯' => '觏', +'覲' => '觐', +'覷' => '觑', +'覺' => '觉', +'覽' => '览', +'覿' => '觌', +'觀' => '观', +'觴' => '觞', +'觶' => '觯', +'觸' => '触', +'訁' => '讠', +'訂' => '订', +'訃' => '讣', +'計' => '计', +'訊' => '讯', +'訌' => '讧', +'討' => '讨', +'訐' => '讦', +'訒' => '讱', +'訓' => '训', +'訕' => '讪', +'訖' => '讫', +'託' => '托', +'記' => '记', +'訛' => '讹', +'訝' => '讶', +'訟' => '讼', +'訢' => '䜣', +'訣' => '诀', +'訥' => '讷', +'訩' => '讻', +'訪' => '访', +'設' => '设', +'許' => '许', +'訴' => '诉', +'訶' => '诃', +'診' => '诊', +'註' => '注', +'詁' => '诂', +'詆' => '诋', +'詎' => '讵', +'詐' => '诈', +'詒' => '诒', +'詔' => '诏', +'評' => '评', +'詖' => '诐', +'詗' => '诇', +'詘' => '诎', +'詛' => '诅', +'詞' => '词', +'詠' => '咏', +'詡' => '诩', +'詢' => '询', +'詣' => '诣', +'試' => '试', +'詩' => '诗', +'詫' => '诧', +'詬' => '诟', +'詭' => '诡', +'詮' => '诠', +'詰' => '诘', +'話' => '话', +'該' => '该', +'詳' => '详', +'詵' => '诜', +'詼' => '诙', +'詿' => '诖', +'誄' => '诔', +'誅' => '诛', +'誆' => '诓', +'誇' => '夸', +'誌' => '志', +'認' => '认', +'誑' => '诳', +'誒' => '诶', +'誕' => '诞', +'誘' => '诱', +'誚' => '诮', +'語' => '语', +'誠' => '诚', +'誡' => '诫', +'誣' => '诬', +'誤' => '误', +'誥' => '诰', +'誦' => '诵', +'誨' => '诲', +'說' => '说', +'説' => '说', +'誰' => '谁', +'課' => '课', +'誶' => '谇', +'誹' => '诽', +'誼' => '谊', +'誾' => '訚', +'調' => '调', +'諂' => '谄', +'諄' => '谆', +'談' => '谈', +'諉' => '诿', +'請' => '请', +'諍' => '诤', +'諏' => '诹', +'諑' => '诼', +'諒' => '谅', +'論' => '论', +'諗' => '谂', +'諛' => '谀', +'諜' => '谍', +'諝' => '谞', +'諞' => '谝', +'諢' => '诨', +'諤' => '谔', +'諦' => '谛', +'諧' => '谐', +'諫' => '谏', +'諭' => '谕', +'諮' => '咨', +'諱' => '讳', +'諳' => '谙', +'諶' => '谌', +'諷' => '讽', +'諸' => '诸', +'諺' => '谚', +'諼' => '谖', +'諾' => '诺', +'謀' => '谋', +'謁' => '谒', +'謂' => '谓', +'謄' => '誊', +'謅' => '诌', +'謊' => '谎', +'謎' => '谜', +'謐' => '谧', +'謔' => '谑', +'謖' => '谡', +'謗' => '谤', +'謙' => '谦', +'謚' => '谥', +'講' => '讲', +'謝' => '谢', +'謠' => '谣', +'謡' => '谣', +'謨' => '谟', +'謫' => '谪', +'謬' => '谬', +'謭' => '谫', +'謳' => '讴', +'謹' => '谨', +'謾' => '谩', +'譅' => '䜧', +'證' => '证', +'譎' => '谲', +'譏' => '讥', +'譖' => '谮', +'識' => '识', +'譙' => '谯', +'譚' => '谭', +'譜' => '谱', +'譫' => '谵', +'譭' => '毁', +'譯' => '译', +'議' => '议', +'譴' => '谴', +'護' => '护', +'譸' => '诪', +'譽' => '誉', +'譾' => '谫', +'讀' => '读', +'變' => '变', +'讎' => '仇', +'讒' => '谗', +'讓' => '让', +'讕' => '谰', +'讖' => '谶', +'讚' => '赞', +'讜' => '谠', +'讞' => '谳', +'豈' => '岂', +'豎' => '竖', +'豐' => '丰', +'豔' => '艳', +'豬' => '猪', +'豶' => '豮', +'貓' => '猫', +'貙' => '䝙', +'貝' => '贝', +'貞' => '贞', +'貟' => '贠', +'負' => '负', +'財' => '财', +'貢' => '贡', +'貧' => '贫', +'貨' => '货', +'販' => '贩', +'貪' => '贪', +'貫' => '贯', +'責' => '责', +'貯' => '贮', +'貰' => '贳', +'貲' => '赀', +'貳' => '贰', +'貴' => '贵', +'貶' => '贬', +'買' => '买', +'貸' => '贷', +'貺' => '贶', +'費' => '费', +'貼' => '贴', +'貽' => '贻', +'貿' => '贸', +'賀' => '贺', +'賁' => '贲', +'賂' => '赂', +'賃' => '赁', +'賄' => '贿', +'賅' => '赅', +'資' => '资', +'賈' => '贾', +'賊' => '贼', +'賑' => '赈', +'賒' => '赊', +'賓' => '宾', +'賕' => '赇', +'賙' => '赒', +'賚' => '赉', +'賜' => '赐', +'賞' => '赏', +'賠' => '赔', +'賡' => '赓', +'賢' => '贤', +'賣' => '卖', +'賤' => '贱', +'賦' => '赋', +'賧' => '赕', +'質' => '质', +'賫' => '赍', +'賬' => '账', +'賭' => '赌', +'賰' => '䞐', +'賴' => '赖', +'賵' => '赗', +'賺' => '赚', +'賻' => '赙', +'購' => '购', +'賽' => '赛', +'賾' => '赜', +'贄' => '贽', +'贅' => '赘', +'贇' => '赟', +'贈' => '赠', +'贊' => '赞', +'贋' => '赝', +'贍' => '赡', +'贏' => '赢', +'贐' => '赆', +'贓' => '赃', +'贔' => '赑', +'贖' => '赎', +'贗' => '赝', +'贛' => '赣', +'贜' => '赃', +'赬' => '赪', +'趕' => '赶', +'趙' => '赵', +'趨' => '趋', +'趲' => '趱', +'跡' => '迹', +'踐' => '践', +'踴' => '踊', +'蹌' => '跄', +'蹕' => '跸', +'蹣' => '蹒', +'蹤' => '踪', +'蹺' => '跷', +'躂' => '跶', +'躉' => '趸', +'躊' => '踌', +'躋' => '跻', +'躍' => '跃', +'躑' => '踯', +'躒' => '跞', +'躓' => '踬', +'躕' => '蹰', +'躚' => '跹', +'躡' => '蹑', +'躥' => '蹿', +'躦' => '躜', +'躪' => '躏', +'軀' => '躯', +'車' => '车', +'軋' => '轧', +'軌' => '轨', +'軍' => '军', +'軑' => '轪', +'軒' => '轩', +'軔' => '轫', +'軛' => '轭', +'軟' => '软', +'軤' => '轷', +'軫' => '轸', +'軲' => '轱', +'軸' => '轴', +'軹' => '轵', +'軺' => '轺', +'軻' => '轲', +'軼' => '轶', +'軾' => '轼', +'較' => '较', +'輅' => '辂', +'輇' => '辁', +'輈' => '辀', +'載' => '载', +'輊' => '轾', +'輒' => '辄', +'輓' => '挽', +'輔' => '辅', +'輕' => '轻', +'輛' => '辆', +'輜' => '辎', +'輝' => '辉', +'輞' => '辋', +'輟' => '辍', +'輥' => '辊', +'輦' => '辇', +'輩' => '辈', +'輪' => '轮', +'輬' => '辌', +'輯' => '辑', +'輳' => '辏', +'輸' => '输', +'輻' => '辐', +'輾' => '辗', +'輿' => '舆', +'轀' => '辒', +'轂' => '毂', +'轄' => '辖', +'轅' => '辕', +'轆' => '辘', +'轉' => '转', +'轍' => '辙', +'轎' => '轿', +'轔' => '辚', +'轟' => '轰', +'轡' => '辔', +'轢' => '轹', +'轤' => '轳', +'辦' => '办', +'辭' => '辞', +'辮' => '辫', +'辯' => '辩', +'農' => '农', +'迴' => '回', +'逕' => '迳', +'這' => '这', +'連' => '连', +'週' => '周', +'進' => '进', +'遊' => '游', +'運' => '运', +'過' => '过', +'達' => '达', +'違' => '违', +'遙' => '遥', +'遜' => '逊', +'遞' => '递', +'遠' => '远', +'遡' => '溯', +'適' => '适', +'遲' => '迟', +'遷' => '迁', +'選' => '选', +'遺' => '遗', +'遼' => '辽', +'邁' => '迈', +'還' => '还', +'邇' => '迩', +'邊' => '边', +'邏' => '逻', +'邐' => '逦', +'郟' => '郏', +'郵' => '邮', +'鄆' => '郓', +'鄉' => '乡', +'鄒' => '邹', +'鄔' => '邬', +'鄖' => '郧', +'鄧' => '邓', +'鄭' => '郑', +'鄰' => '邻', +'鄲' => '郸', +'鄴' => '邺', +'鄶' => '郐', +'鄺' => '邝', +'酇' => '酂', +'酈' => '郦', +'醖' => '酝', +'醜' => '丑', +'醞' => '酝', +'醣' => '糖', +'醫' => '医', +'醬' => '酱', +'醱' => '酦', +'釀' => '酿', +'釁' => '衅', +'釃' => '酾', +'釅' => '酽', +'釋' => '释', +'釐' => '厘', +'釒' => '钅', +'釓' => '钆', +'釔' => '钇', +'釕' => '钌', +'釗' => '钊', +'釘' => '钉', +'釙' => '钋', +'針' => '针', +'釣' => '钓', +'釤' => '钐', +'釧' => '钏', +'釩' => '钒', +'釳' => '𨰿', +'釵' => '钗', +'釷' => '钍', +'釹' => '钕', +'釺' => '钎', +'釾' => '䥺', +'鈀' => '钯', +'鈁' => '钫', +'鈃' => '钘', +'鈄' => '钭', +'鈈' => '钚', +'鈉' => '钠', +'鈋' => '𨱂', +'鈍' => '钝', +'鈎' => '钩', +'鈐' => '钤', +'鈑' => '钣', +'鈒' => '钑', +'鈔' => '钞', +'鈕' => '钮', +'鈞' => '钧', +'鈠' => '𨱁', +'鈣' => '钙', +'鈥' => '钬', +'鈦' => '钛', +'鈧' => '钪', +'鈮' => '铌', +'鈯' => '𨱄', +'鈰' => '铈', +'鈲' => '𨱃', +'鈳' => '钶', +'鈴' => '铃', +'鈷' => '钴', +'鈸' => '钹', +'鈹' => '铍', +'鈺' => '钰', +'鈽' => '钸', +'鈾' => '铀', +'鈿' => '钿', +'鉀' => '钾', +'鉁' => '𨱅', +'鉅' => '钜', +'鉈' => '铊', +'鉉' => '铉', +'鉋' => '铇', +'鉍' => '铋', +'鉑' => '铂', +'鉕' => '钷', +'鉗' => '钳', +'鉚' => '铆', +'鉛' => '铅', +'鉞' => '钺', +'鉢' => '钵', +'鉤' => '钩', +'鉦' => '钲', +'鉬' => '钼', +'鉭' => '钽', +'鉶' => '铏', +'鉸' => '铰', +'鉺' => '铒', +'鉻' => '铬', +'鉿' => '铪', +'銀' => '银', +'銃' => '铳', +'銅' => '铜', +'銍' => '铚', +'銑' => '铣', +'銓' => '铨', +'銖' => '铢', +'銘' => '铭', +'銚' => '铫', +'銛' => '铦', +'銜' => '衔', +'銠' => '铑', +'銣' => '铷', +'銥' => '铱', +'銦' => '铟', +'銨' => '铵', +'銩' => '铥', +'銪' => '铕', +'銫' => '铯', +'銬' => '铐', +'銱' => '铞', +'銳' => '锐', +'銶' => '𨱇', +'銷' => '销', +'銹' => '锈', +'銻' => '锑', +'銼' => '锉', +'鋁' => '铝', +'鋃' => '锒', +'鋅' => '锌', +'鋇' => '钡', +'鋉' => '𨱈', +'鋌' => '铤', +'鋏' => '铗', +'鋒' => '锋', +'鋙' => '铻', +'鋝' => '锊', +'鋟' => '锓', +'鋣' => '铘', +'鋤' => '锄', +'鋥' => '锃', +'鋦' => '锔', +'鋨' => '锇', +'鋩' => '铓', +'鋪' => '铺', +'鋭' => '锐', +'鋮' => '铖', +'鋯' => '锆', +'鋰' => '锂', +'鋱' => '铽', +'鋶' => '锍', +'鋸' => '锯', +'鋼' => '钢', +'錁' => '锞', +'錂' => '𨱋', +'錄' => '录', +'錆' => '锖', +'錇' => '锫', +'錈' => '锩', +'錏' => '铔', +'錐' => '锥', +'錒' => '锕', +'錕' => '锟', +'錘' => '锤', +'錙' => '锱', +'錚' => '铮', +'錛' => '锛', +'錟' => '锬', +'錠' => '锭', +'錡' => '锜', +'錢' => '钱', +'錦' => '锦', +'錨' => '锚', +'錩' => '锠', +'錫' => '锡', +'錮' => '锢', +'錯' => '错', +'録' => '录', +'錳' => '锰', +'錶' => '表', +'錸' => '铼', +'鍀' => '锝', +'鍁' => '锨', +'鍃' => '锪', +'鍄' => '𨱉', +'鍆' => '钔', +'鍇' => '锴', +'鍈' => '锳', +'鍊' => '炼', +'鍋' => '锅', +'鍍' => '镀', +'鍔' => '锷', +'鍘' => '铡', +'鍚' => '钖', +'鍛' => '锻', +'鍠' => '锽', +'鍤' => '锸', +'鍥' => '锲', +'鍩' => '锘', +'鍬' => '锹', +'鍮' => '𨱎', +'鍰' => '锾', +'鍵' => '键', +'鍶' => '锶', +'鍺' => '锗', +'鍾' => '钟', +'鎂' => '镁', +'鎄' => '锿', +'鎇' => '镅', +'鎊' => '镑', +'鎌' => '镰', +'鎔' => '镕', +'鎖' => '锁', +'鎘' => '镉', +'鎚' => '锤', +'鎛' => '镈', +'鎝' => '𨱏', +'鎡' => '镃', +'鎢' => '钨', +'鎣' => '蓥', +'鎦' => '镏', +'鎧' => '铠', +'鎩' => '铩', +'鎪' => '锼', +'鎬' => '镐', +'鎮' => '镇', +'鎯' => '𨱍', +'鎰' => '镒', +'鎲' => '镋', +'鎳' => '镍', +'鎵' => '镓', +'鎷' => '𨰾', +'鎸' => '镌', +'鎿' => '镎', +'鏃' => '镞', +'鏆' => '𨱌', +'鏇' => '镟', +'鏈' => '链', +'鏉' => '𨱒', +'鏌' => '镆', +'鏍' => '镙', +'鏐' => '镠', +'鏑' => '镝', +'鏗' => '铿', +'鏘' => '锵', +'鏚' => '戚', +'鏜' => '镗', +'鏝' => '镘', +'鏞' => '镛', +'鏟' => '铲', +'鏡' => '镜', +'鏢' => '镖', +'鏤' => '镂', +'鏨' => '錾', +'鏰' => '镚', +'鏵' => '铧', +'鏷' => '镤', +'鏹' => '镪', +'鏺' => '䥽', +'鏽' => '锈', +'鐃' => '铙', +'鐋' => '铴', +'鐎' => '𨱓', +'鐏' => '𨱔', +'鐐' => '镣', +'鐒' => '铹', +'鐓' => '镦', +'鐔' => '镡', +'鐘' => '钟', +'鐙' => '镫', +'鐝' => '镢', +'鐠' => '镨', +'鐥' => '䦅', +'鐦' => '锎', +'鐧' => '锏', +'鐨' => '镄', +'鐫' => '镌', +'鐮' => '镰', +'鐯' => '䦃', +'鐲' => '镯', +'鐳' => '镭', +'鐵' => '铁', +'鐶' => '镮', +'鐸' => '铎', +'鐺' => '铛', +'鐿' => '镱', +'鑄' => '铸', +'鑊' => '镬', +'鑌' => '镔', +'鑑' => '鉴', +'鑒' => '鉴', +'鑔' => '镲', +'鑕' => '锧', +'鑞' => '镴', +'鑠' => '铄', +'鑣' => '镳', +'鑥' => '镥', +'鑭' => '镧', +'鑰' => '钥', +'鑱' => '镵', +'鑲' => '镶', +'鑷' => '镊', +'鑹' => '镩', +'鑼' => '锣', +'鑽' => '钻', +'鑾' => '銮', +'鑿' => '凿', +'钁' => '镢', +'镟' => '旋', +'長' => '长', +'門' => '门', +'閂' => '闩', +'閃' => '闪', +'閆' => '闫', +'閈' => '闬', +'閉' => '闭', +'開' => '开', +'閌' => '闶', +'閍' => '𨸂', +'閎' => '闳', +'閏' => '闰', +'閐' => '𨸃', +'閑' => '闲', +'閒' => '闲', +'間' => '间', +'閔' => '闵', +'閘' => '闸', +'閡' => '阂', +'閣' => '阁', +'閤' => '合', +'閥' => '阀', +'閨' => '闺', +'閩' => '闽', +'閫' => '阃', +'閬' => '阆', +'閭' => '闾', +'閱' => '阅', +'閲' => '阅', +'閶' => '阊', +'閹' => '阉', +'閻' => '阎', +'閼' => '阏', +'閽' => '阍', +'閾' => '阈', +'閿' => '阌', +'闃' => '阒', +'闆' => '板', +'闈' => '闱', +'闊' => '阔', +'闋' => '阕', +'闌' => '阑', +'闍' => '阇', +'闐' => '阗', +'闒' => '阘', +'闓' => '闿', +'闔' => '阖', +'闕' => '阙', +'闖' => '闯', +'關' => '关', +'闞' => '阚', +'闠' => '阓', +'闡' => '阐', +'闢' => '辟', +'闤' => '阛', +'闥' => '闼', +'陘' => '陉', +'陝' => '陕', +'陞' => '升', +'陣' => '阵', +'陰' => '阴', +'陳' => '陈', +'陸' => '陆', +'陽' => '阳', +'隉' => '陧', +'隊' => '队', +'階' => '阶', +'隕' => '陨', +'際' => '际', +'隨' => '随', +'險' => '险', +'隱' => '隐', +'隴' => '陇', +'隸' => '隶', +'隻' => '只', +'雋' => '隽', +'雖' => '虽', +'雙' => '双', +'雛' => '雏', +'雜' => '杂', +'雞' => '鸡', +'離' => '离', +'難' => '难', +'雲' => '云', +'電' => '电', +'霢' => '霡', +'霧' => '雾', +'霽' => '霁', +'靂' => '雳', +'靄' => '霭', +'靈' => '灵', +'靚' => '靓', +'靜' => '静', +'靦' => '腼', +'靨' => '靥', +'鞀' => '鼗', +'鞏' => '巩', +'鞝' => '绱', +'鞦' => '秋', +'鞽' => '鞒', +'韁' => '缰', +'韃' => '鞑', +'韆' => '千', +'韉' => '鞯', +'韋' => '韦', +'韌' => '韧', +'韍' => '韨', +'韓' => '韩', +'韙' => '韪', +'韜' => '韬', +'韞' => '韫', +'韻' => '韵', +'響' => '响', +'頁' => '页', +'頂' => '顶', +'頃' => '顷', +'項' => '项', +'順' => '顺', +'頇' => '顸', +'須' => '须', +'頊' => '顼', +'頌' => '颂', +'頎' => '颀', +'頏' => '颃', +'預' => '预', +'頑' => '顽', +'頒' => '颁', +'頓' => '顿', +'頗' => '颇', +'領' => '领', +'頜' => '颌', +'頡' => '颉', +'頤' => '颐', +'頦' => '颏', +'頭' => '头', +'頮' => '颒', +'頰' => '颊', +'頲' => '颋', +'頴' => '颕', +'頷' => '颔', +'頸' => '颈', +'頹' => '颓', +'頻' => '频', +'頽' => '颓', +'顃' => '𩖖', +'顆' => '颗', +'題' => '题', +'額' => '额', +'顎' => '颚', +'顏' => '颜', +'顒' => '颙', +'顓' => '颛', +'顔' => '颜', +'願' => '愿', +'顙' => '颡', +'顛' => '颠', +'類' => '类', +'顢' => '颟', +'顥' => '颢', +'顧' => '顾', +'顫' => '颤', +'顬' => '颥', +'顯' => '显', +'顰' => '颦', +'顱' => '颅', +'顳' => '颞', +'顴' => '颧', +'風' => '风', +'颭' => '飐', +'颮' => '飑', +'颯' => '飒', +'颰' => '𩙥', +'颱' => '台', +'颳' => '刮', +'颶' => '飓', +'颷' => '𩙪', +'颸' => '飔', +'颺' => '飏', +'颻' => '飖', +'颼' => '飕', +'颾' => '𩙫', +'飀' => '飗', +'飄' => '飘', +'飆' => '飙', +'飈' => '飚', +'飛' => '飞', +'飠' => '饣', +'飢' => '饥', +'飣' => '饤', +'飥' => '饦', +'飩' => '饨', +'飪' => '饪', +'飫' => '饫', +'飭' => '饬', +'飯' => '饭', +'飱' => '飧', +'飲' => '饮', +'飴' => '饴', +'飼' => '饲', +'飽' => '饱', +'飾' => '饰', +'飿' => '饳', +'餃' => '饺', +'餄' => '饸', +'餅' => '饼', +'餉' => '饷', +'養' => '养', +'餌' => '饵', +'餎' => '饹', +'餏' => '饻', +'餑' => '饽', +'餒' => '馁', +'餓' => '饿', +'餕' => '馂', +'餖' => '饾', +'餘' => '余', +'餚' => '肴', +'餛' => '馄', +'餜' => '馃', +'餞' => '饯', +'餡' => '馅', +'館' => '馆', +'餱' => '糇', +'餳' => '饧', +'餵' => '喂', +'餶' => '馉', +'餷' => '馇', +'餸' => '𩠌', +'餺' => '馎', +'餼' => '饩', +'餾' => '馏', +'餿' => '馊', +'饁' => '馌', +'饃' => '馍', +'饅' => '馒', +'饈' => '馐', +'饉' => '馑', +'饊' => '馓', +'饋' => '馈', +'饌' => '馔', +'饑' => '饥', +'饒' => '饶', +'饗' => '飨', +'饜' => '餍', +'饞' => '馋', +'饢' => '馕', +'馬' => '马', +'馭' => '驭', +'馮' => '冯', +'馱' => '驮', +'馳' => '驰', +'馴' => '驯', +'馹' => '驲', +'駁' => '驳', +'駎' => '𩧨', +'駐' => '驻', +'駑' => '驽', +'駒' => '驹', +'駔' => '驵', +'駕' => '驾', +'駘' => '骀', +'駙' => '驸', +'駚' => '𩧫', +'駛' => '驶', +'駝' => '驼', +'駟' => '驷', +'駡' => '骂', +'駢' => '骈', +'駧' => '𩧲', +'駩' => '𩧴', +'駭' => '骇', +'駰' => '骃', +'駱' => '骆', +'駶' => '𩧺', +'駸' => '骎', +'駿' => '骏', +'騁' => '骋', +'騂' => '骍', +'騅' => '骓', +'騌' => '骔', +'騍' => '骒', +'騎' => '骑', +'騏' => '骐', +'騔' => '𩨀', +'騖' => '骛', +'騙' => '骗', +'騚' => '𩨊', +'騝' => '𩨃', +'騟' => '𩨈', +'騤' => '骙', +'騧' => '䯄', +'騪' => '𩨄', +'騫' => '骞', +'騭' => '骘', +'騮' => '骝', +'騰' => '腾', +'騶' => '驺', +'騷' => '骚', +'騸' => '骟', +'騾' => '骡', +'驀' => '蓦', +'驁' => '骜', +'驂' => '骖', +'驃' => '骠', +'驄' => '骢', +'驅' => '驱', +'驊' => '骅', +'驋' => '𩧯', +'驌' => '骕', +'驍' => '骁', +'驏' => '骣', +'驕' => '骄', +'驗' => '验', +'驚' => '惊', +'驛' => '驿', +'驟' => '骤', +'驢' => '驴', +'驤' => '骧', +'驥' => '骥', +'驦' => '骦', +'驪' => '骊', +'驫' => '骉', +'骯' => '肮', +'髏' => '髅', +'髒' => '脏', +'體' => '体', +'髕' => '髌', +'髖' => '髋', +'髮' => '发', +'鬆' => '松', +'鬍' => '胡', +'鬚' => '须', +'鬢' => '鬓', +'鬥' => '斗', +'鬧' => '闹', +'鬨' => '哄', +'鬩' => '阋', +'鬮' => '阄', +'鬱' => '郁', +'魎' => '魉', +'魘' => '魇', +'魚' => '鱼', +'魛' => '鱽', +'魢' => '鱾', +'魥' => '𩽹', +'魨' => '鲀', +'魯' => '鲁', +'魴' => '鲂', +'魷' => '鱿', +'魺' => '鲄', +'鮁' => '鲅', +'鮃' => '鲆', +'鮊' => '鲌', +'鮋' => '鲉', +'鮍' => '鲏', +'鮎' => '鲇', +'鮐' => '鲐', +'鮑' => '鲍', +'鮒' => '鲋', +'鮓' => '鲊', +'鮕' => '𩾀', +'鮚' => '鲒', +'鮜' => '鲘', +'鮝' => '鲞', +'鮞' => '鲕', +'鮟' => '𩽾', +'鮣' => '䲟', +'鮦' => '鲖', +'鮪' => '鲔', +'鮫' => '鲛', +'鮭' => '鲑', +'鮮' => '鲜', +'鮳' => '鲓', +'鮶' => '鲪', +'鮸' => '𩾃', +'鮺' => '鲝', +'鯀' => '鲧', +'鯁' => '鲠', +'鯄' => '𩾁', +'鯇' => '鲩', +'鯉' => '鲤', +'鯊' => '鲨', +'鯒' => '鲬', +'鯔' => '鲻', +'鯕' => '鲯', +'鯖' => '鲭', +'鯗' => '鲞', +'鯛' => '鲷', +'鯝' => '鲴', +'鯡' => '鲱', +'鯢' => '鲵', +'鯤' => '鲲', +'鯧' => '鲳', +'鯨' => '鲸', +'鯪' => '鲮', +'鯫' => '鲰', +'鯰' => '鲇', +'鯱' => '𩾇', +'鯴' => '鲺', +'鯶' => '𩽼', +'鯷' => '鳀', +'鯽' => '鲫', +'鯿' => '鳊', +'鰁' => '鳈', +'鰂' => '鲗', +'鰃' => '鳂', +'鰆' => '䲠', +'鰈' => '鲽', +'鰉' => '鳇', +'鰌' => '䲡', +'鰍' => '鳅', +'鰏' => '鲾', +'鰐' => '鳄', +'鰒' => '鳆', +'鰓' => '鳃', +'鰜' => '鳒', +'鰟' => '鳑', +'鰠' => '鳋', +'鰣' => '鲥', +'鰥' => '鳏', +'鰧' => '䲢', +'鰨' => '鳎', +'鰩' => '鳐', +'鰭' => '鳍', +'鰮' => '鳁', +'鰱' => '鲢', +'鰲' => '鳌', +'鰳' => '鳓', +'鰵' => '鳘', +'鰷' => '鲦', +'鰹' => '鲣', +'鰺' => '鲹', +'鰻' => '鳗', +'鰼' => '鳛', +'鰾' => '鳔', +'鱂' => '鳉', +'鱅' => '鳙', +'鱇' => '𩾌', +'鱈' => '鳕', +'鱉' => '鳖', +'鱒' => '鳟', +'鱔' => '鳝', +'鱖' => '鳜', +'鱗' => '鳞', +'鱘' => '鲟', +'鱝' => '鲼', +'鱟' => '鲎', +'鱠' => '鲙', +'鱣' => '鳣', +'鱤' => '鳡', +'鱧' => '鳢', +'鱨' => '鲿', +'鱭' => '鲚', +'鱯' => '鳠', +'鱷' => '鳄', +'鱸' => '鲈', +'鱺' => '鲡', +'鳥' => '鸟', +'鳧' => '凫', +'鳩' => '鸠', +'鳬' => '凫', +'鳲' => '鸤', +'鳳' => '凤', +'鳴' => '鸣', +'鳶' => '鸢', +'鳼' => '𪉃', +'鳾' => '䴓', +'鴆' => '鸩', +'鴇' => '鸨', +'鴉' => '鸦', +'鴒' => '鸰', +'鴕' => '鸵', +'鴛' => '鸳', +'鴜' => '𪉈', +'鴝' => '鸲', +'鴞' => '鸮', +'鴟' => '鸱', +'鴣' => '鸪', +'鴦' => '鸯', +'鴨' => '鸭', +'鴯' => '鸸', +'鴰' => '鸹', +'鴲' => '𪉆', +'鴴' => '鸻', +'鴷' => '䴕', +'鴻' => '鸿', +'鴿' => '鸽', +'鵁' => '䴔', +'鵂' => '鸺', +'鵃' => '鸼', +'鵐' => '鹀', +'鵑' => '鹃', +'鵒' => '鹆', +'鵓' => '鹁', +'鵚' => '𪉍', +'鵜' => '鹈', +'鵝' => '鹅', +'鵠' => '鹄', +'鵡' => '鹉', +'鵪' => '鹌', +'鵬' => '鹏', +'鵮' => '鹐', +'鵯' => '鹎', +'鵰' => '雕', +'鵲' => '鹊', +'鵷' => '鹓', +'鵾' => '鹍', +'鶄' => '䴖', +'鶇' => '鸫', +'鶉' => '鹑', +'鶊' => '鹒', +'鶓' => '鹋', +'鶖' => '鹙', +'鶘' => '鹕', +'鶚' => '鹗', +'鶡' => '鹖', +'鶥' => '鹛', +'鶩' => '鹜', +'鶪' => '䴗', +'鶬' => '鸧', +'鶯' => '莺', +'鶲' => '鹟', +'鶴' => '鹤', +'鶹' => '鹠', +'鶺' => '鹡', +'鶻' => '鹘', +'鶼' => '鹣', +'鶿' => '鹚', +'鷀' => '鹚', +'鷁' => '鹢', +'鷂' => '鹞', +'鷄' => '鸡', +'鷈' => '䴘', +'鷊' => '鹝', +'鷓' => '鹧', +'鷔' => '𪉑', +'鷖' => '鹥', +'鷗' => '鸥', +'鷙' => '鸷', +'鷚' => '鹨', +'鷥' => '鸶', +'鷦' => '鹪', +'鷨' => '𪉊', +'鷫' => '鹔', +'鷯' => '鹩', +'鷲' => '鹫', +'鷳' => '鹇', +'鷸' => '鹬', +'鷹' => '鹰', +'鷺' => '鹭', +'鷽' => '鸴', +'鷿' => '䴙', +'鸂' => '㶉', +'鸇' => '鹯', +'鸌' => '鹱', +'鸏' => '鹲', +'鸕' => '鸬', +'鸘' => '鹴', +'鸚' => '鹦', +'鸛' => '鹳', +'鸝' => '鹂', +'鸞' => '鸾', +'鹵' => '卤', +'鹹' => '咸', +'鹺' => '鹾', +'鹼' => '碱', +'鹽' => '盐', +'麗' => '丽', +'麥' => '麦', +'麨' => '𪎊', +'麩' => '麸', +'麪' => '面', +'麫' => '面', +'麯' => '曲', +'麲' => '𪎉', +'麳' => '𪎌', +'麴' => '曲', +'麵' => '面', +'麼' => '么', +'麽' => '么', +'黃' => '黄', +'黌' => '黉', +'點' => '点', +'黨' => '党', +'黲' => '黪', +'黴' => '霉', +'黶' => '黡', +'黷' => '黩', +'黽' => '黾', +'黿' => '鼋', +'鼉' => '鼍', +'鼕' => '冬', +'鼴' => '鼹', +'齊' => '齐', +'齋' => '斋', +'齎' => '赍', +'齏' => '齑', +'齒' => '齿', +'齔' => '龀', +'齕' => '龁', +'齗' => '龂', +'齙' => '龅', +'齜' => '龇', +'齟' => '龃', +'齠' => '龆', +'齡' => '龄', +'齣' => '出', +'齦' => '龈', +'齪' => '龊', +'齬' => '龉', +'齲' => '龋', +'齶' => '腭', +'齷' => '龌', +'龍' => '龙', +'龎' => '厐', +'龐' => '庞', +'龔' => '龚', +'龕' => '龛', +'龜' => '龟', +'𡞵' => '㛟', +'𡠹' => '㛿', +'𡢃' => '㛠', +'𡻕' => '岁', +'𤪺' => '㻘', +'𤫩' => '㻏', +'𦪙' => '䑽', +'𧜵' => '䙊', +'𧝞' => '䘛', +'𧩙' => '䜥', +'𧵳' => '䞌', +'𨋢' => '䢂', +'𨥛' => '𨱀', +'𨦫' => '䦀', +'𨧜' => '䦁', +'𨧱' => '𨱊', +'𨫒' => '𨱐', +'𨮂' => '𨱕', +'𨯅' => '䥿', +'𩎢' => '𩏾', +'𩏪' => '𩏽', +'𩓣' => '𩖕', +'𩗀' => '𩙦', +'𩗡' => '𩙧', +'𩘀' => '𩙩', +'𩘝' => '𩙭', +'𩘹' => '𩙨', +'𩘺' => '𩙬', +'𩙈' => '𩙰', +'𩜦' => '𩠆', +'𩝔' => '𩠋', +'𩞯' => '䭪', +'𩟐' => '𩠅', +'𩡺' => '𩧦', +'𩢡' => '𩧬', +'𩢴' => '𩧵', +'𩢸' => '𩧳', +'𩢾' => '𩧮', +'𩣏' => '𩧶', +'𩣑' => '䯃', +'𩣵' => '𩧻', +'𩣺' => '𩧼', +'𩤊' => '𩧩', +'𩤙' => '𩨆', +'𩤲' => '𩨉', +'𩤸' => '𩨅', +'𩥄' => '𩨋', +'𩥇' => '𩨍', +'𩥉' => '𩧱', +'𩥑' => '𩨌', +'𩧆' => '𩨐', +'𩵩' => '𩽺', +'𩵹' => '𩽻', +'𩶘' => '䲞', +'𩶰' => '𩽿', +'𩶱' => '𩽽', +'𩷰' => '𩾄', +'𩸃' => '𩾅', +'𩸦' => '𩾆', +'𩽇' => '𩾎', +'𩿪' => '𪉄', +'𪀦' => '𪉅', +'𪀾' => '𪉋', +'𪁈' => '𪉉', +'𪁖' => '𪉌', +'𪂆' => '𪉎', +'𪃍' => '𪉐', +'𪃏' => '𪉏', +'𪄆' => '𪉔', +'𪄕' => '𪉒', +'𪇳' => '𪉕', +'𪘀' => '𪚏', +'𪘯' => '𪚐', +'《周易乾' => '《周易乾', +'《易乾' => '《易乾', +'不著痕跡' => '不着痕迹', +'不著邊際' => '不着边际', +'與著' => '与着', +'與著書' => '与著书', +'與著作' => '与著作', +'與著名' => '与著名', +'與著錄' => '与著录', +'與著稱' => '与著称', +'與著者' => '与著者', +'與著述' => '与著述', +'丑著' => '丑着', +'丑著書' => '丑著书', +'丑著作' => '丑著作', +'丑著名' => '丑著名', +'丑著錄' => '丑著录', +'丑著稱' => '丑著称', +'丑著者' => '丑著者', +'丑著述' => '丑著述', +'專著' => '专著', +'臨著' => '临着', +'臨著書' => '临著书', +'臨著作' => '临著作', +'臨著名' => '临著名', +'臨著錄' => '临著录', +'臨著稱' => '临著称', +'臨著者' => '临著者', +'臨著述' => '临著述', +'麗著' => '丽着', +'麗著書' => '丽著书', +'麗著作' => '丽著作', +'麗著名' => '丽著名', +'麗著錄' => '丽著录', +'麗著稱' => '丽著称', +'麗著者' => '丽著者', +'麗著述' => '丽著述', +'樂著' => '乐着', +'樂著書' => '乐著书', +'樂著作' => '乐著作', +'樂著名' => '乐著名', +'樂著錄' => '乐著录', +'樂著稱' => '乐著称', +'樂著者' => '乐著者', +'樂著述' => '乐著述', +'乘著' => '乘着', +'乘著書' => '乘著书', +'乘著作' => '乘著作', +'乘著名' => '乘著名', +'乘著錄' => '乘著录', +'乘著稱' => '乘著称', +'乘著者' => '乘著者', +'乘著述' => '乘著述', +'乾上乾下' => '乾上乾下', +'乾為天' => '乾为天', +'乾為陽' => '乾为阳', +'乾九' => '乾九', +'乾乾' => '乾乾', +'乾亨' => '乾亨', +'乾儀' => '乾仪', +'乾仪' => '乾仪', +'乾位' => '乾位', +'乾健' => '乾健', +'乾元' => '乾元', +'乾光' => '乾光', +'乾兴' => '乾兴', +'乾興' => '乾兴', +'乾冈' => '乾冈', +'乾岡' => '乾冈', +'乾劉' => '乾刘', +'乾刘' => '乾刘', +'乾剛' => '乾刚', +'乾刚' => '乾刚', +'乾化' => '乾化', +'乾卦' => '乾卦', +'乾县' => '乾县', +'乾縣' => '乾县', +'乾台' => '乾台', +'乾吉' => '乾吉', +'乾啟' => '乾启', +'乾启' => '乾启', +'乾命' => '乾命', +'乾和' => '乾和', +'乾嘉' => '乾嘉', +'乾圖' => '乾图', +'乾图' => '乾图', +'乾坤' => '乾坤', +'乾城' => '乾城', +'乾基' => '乾基', +'乾始' => '乾始', +'乾姓' => '乾姓', +'乾寧' => '乾宁', +'乾宁' => '乾宁', +'乾宅' => '乾宅', +'乾宇' => '乾宇', +'乾安' => '乾安', +'乾定' => '乾定', +'乾封' => '乾封', +'乾居' => '乾居', +'乾崗' => '乾岗', +'乾岗' => '乾岗', +'乾巛' => '乾巛', +'乾州' => '乾州', +'乾式' => '乾式', +'乾錄' => '乾录', +'乾录' => '乾录', +'乾律' => '乾律', +'乾德' => '乾德', +'乾心' => '乾心', +'乾文' => '乾文', +'乾斷' => '乾断', +'乾断' => '乾断', +'乾方' => '乾方', +'乾施' => '乾施', +'乾旦' => '乾旦', +'乾明' => '乾明', +'乾昧' => '乾昧', +'乾暉' => '乾晖', +'乾晖' => '乾晖', +'乾景' => '乾景', +'乾晷' => '乾晷', +'乾曜' => '乾曜', +'乾构' => '乾构', +'乾構' => '乾构', +'乾枢' => '乾枢', +'乾樞' => '乾枢', +'乾栋' => '乾栋', +'乾棟' => '乾栋', +'乾步' => '乾步', +'乾氏' => '乾氏', +'乾泉' => '乾泉', +'乾清宮' => '乾清宫', +'乾清宫' => '乾清宫', +'乾渥' => '乾渥', +'乾靈' => '乾灵', +'乾灵' => '乾灵', +'乾男' => '乾男', +'乾皋' => '乾皋', +'乾盛世' => '乾盛世', +'乾矢' => '乾矢', +'乾祐' => '乾祐', +'乾穹' => '乾穹', +'乾竇' => '乾窦', +'乾窦' => '乾窦', +'乾竺' => '乾竺', +'乾篤' => '乾笃', +'乾笃' => '乾笃', +'乾符' => '乾符', +'乾策' => '乾策', +'乾精' => '乾精', +'乾紅' => '乾红', +'乾红' => '乾红', +'乾綱' => '乾纲', +'乾纲' => '乾纲', +'乾纽' => '乾纽', +'乾紐' => '乾纽', +'乾絡' => '乾络', +'乾络' => '乾络', +'乾統' => '乾统', +'乾统' => '乾统', +'乾維' => '乾维', +'乾维' => '乾维', +'乾羅' => '乾罗', +'乾罗' => '乾罗', +'乾花' => '乾花', +'乾蔭' => '乾荫', +'乾荫' => '乾荫', +'乾行' => '乾行', +'乾衡' => '乾衡', +'乾覆' => '乾覆', +'乾象' => '乾象', +'乾象歷' => '乾象历', +'乾象历' => '乾象历', +'乾貞' => '乾贞', +'乾贞' => '乾贞', +'乾貺' => '乾贶', +'乾贶' => '乾贶', +'乾车' => '乾车', +'乾車' => '乾车', +'乾轴' => '乾轴', +'乾軸' => '乾轴', +'乾造' => '乾造', +'乾道' => '乾道', +'乾鑒' => '乾鉴', +'乾鉴' => '乾鉴', +'乾钧' => '乾钧', +'乾鈞' => '乾钧', +'乾闼' => '乾闼', +'乾闥' => '乾闼', +'乾陀' => '乾陀', +'乾陵' => '乾陵', +'乾隆' => '乾隆', +'乾音' => '乾音', +'乾顾' => '乾顾', +'乾顧' => '乾顾', +'乾风' => '乾风', +'乾風' => '乾风', +'乾首' => '乾首', +'乾馬' => '乾马', +'乾马' => '乾马', +'乾鵠' => '乾鹄', +'乾鹄' => '乾鹄', +'乾鵲' => '乾鹊', +'乾鹊' => '乾鹊', +'乾龍' => '乾龙', +'乾龙' => '乾龙', +'乾,健也' => '乾,健也', +'乾,天也' => '乾,天也', +'爭著' => '争着', +'爭著書' => '争著书', +'爭著作' => '争著作', +'爭著名' => '争著名', +'爭著錄' => '争著录', +'爭著稱' => '争著称', +'爭著者' => '争著者', +'爭著述' => '争著述', +'五箇山' => '五箇山', +'亮著' => '亮着', +'亮著書' => '亮著书', +'亮著作' => '亮著作', +'亮著名' => '亮著名', +'亮著錄' => '亮著录', +'亮著稱' => '亮著称', +'亮著者' => '亮著者', +'亮著述' => '亮著述', +'仗著' => '仗着', +'仗著書' => '仗著书', +'仗著作' => '仗著作', +'仗著名' => '仗著名', +'仗著錄' => '仗著录', +'仗著稱' => '仗著称', +'仗著者' => '仗著者', +'仗著述' => '仗著述', +'代表著' => '代表着', +'代表著書' => '代表著书', +'代表著作' => '代表著作', +'代表著名' => '代表著名', +'代表著錄' => '代表著录', +'代表著稱' => '代表著称', +'代表著者' => '代表著者', +'代表著述' => '代表著述', +'以微知著' => '以微知著', +'仰屋著書' => '仰屋著书', +'彷彿' => '仿佛', +'夥計' => '伙计', +'伴著' => '伴着', +'伴著書' => '伴著书', +'伴著作' => '伴著作', +'伴著名' => '伴著名', +'伴著錄' => '伴著录', +'伴著稱' => '伴著称', +'伴著者' => '伴著者', +'伴著述' => '伴著述', +'低著' => '低着', +'低著書' => '低著书', +'低著作' => '低著作', +'低著名' => '低著名', +'低著錄' => '低著录', +'低著稱' => '低著称', +'低著者' => '低著者', +'低著述' => '低著述', +'住著' => '住着', +'住著書' => '住著书', +'住著作' => '住著作', +'住著名' => '住著名', +'住著錄' => '住著录', +'住著稱' => '住著称', +'住著者' => '住著者', +'住著述' => '住著述', +'佛頭著糞' => '佛头著粪', +'侏儸紀' => '侏罗纪', +'側著' => '侧着', +'側著書' => '侧著书', +'側著作' => '侧著作', +'側著名' => '侧著名', +'側著錄' => '侧著录', +'側著稱' => '侧著称', +'側著者' => '侧著者', +'側著述' => '侧著述', +'保障著' => '保障着', +'保障著書' => '保障著书', +'保障著作' => '保障著作', +'保障著名' => '保障著名', +'保障著錄' => '保障著录', +'保障著稱' => '保障著称', +'保障著者' => '保障著者', +'保障著述' => '保障著述', +'信著' => '信着', +'信著書' => '信著书', +'信著作' => '信著作', +'信著名' => '信著名', +'信著錄' => '信著录', +'信著稱' => '信著称', +'信著者' => '信著者', +'信著述' => '信著述', +'候著' => '候着', +'候著書' => '候著书', +'候著作' => '候著作', +'候著名' => '候著名', +'候著錄' => '候著录', +'候著稱' => '候著称', +'候著者' => '候著者', +'候著述' => '候著述', +'藉助' => '借助', +'藉口' => '借口', +'藉手' => '借手', +'藉故' => '借故', +'藉機' => '借机', +'藉此' => '借此', +'藉由' => '借由', +'借著' => '借着', +'藉端' => '借端', +'借著書' => '借著书', +'借著作' => '借著作', +'借著名' => '借著名', +'借著錄' => '借著录', +'借著稱' => '借著称', +'借著者' => '借著者', +'借著述' => '借著述', +'藉詞' => '借词', +'做著' => '做着', +'做著書' => '做著书', +'做著作' => '做著作', +'做著名' => '做著名', +'做著錄' => '做著录', +'做著稱' => '做著称', +'做著者' => '做著者', +'做著述' => '做著述', +'偷著' => '偷着', +'偷著書' => '偷著书', +'偷著作' => '偷著作', +'偷著名' => '偷著名', +'偷著錄' => '偷著录', +'偷著稱' => '偷著称', +'偷著者' => '偷著者', +'偷著述' => '偷著述', +'傢俬' => '傢俬', +'光著' => '光着', +'光著書' => '光著书', +'光著作' => '光著作', +'光著名' => '光著名', +'光著錄' => '光著录', +'光著稱' => '光著称', +'光著者' => '光著者', +'光著述' => '光著述', +'關著' => '关着', +'關著書' => '关著书', +'關著作' => '关著作', +'關著名' => '关著名', +'關著錄' => '关著录', +'關著稱' => '关著称', +'關著者' => '关著者', +'關著述' => '关著述', +'冀著' => '冀着', +'冀著書' => '冀著书', +'冀著作' => '冀著作', +'冀著名' => '冀著名', +'冀著錄' => '冀著录', +'冀著稱' => '冀著称', +'冀著者' => '冀著者', +'冀著述' => '冀著述', +'冒著' => '冒着', +'冒著書' => '冒著书', +'冒著作' => '冒著作', +'冒著名' => '冒著名', +'冒著錄' => '冒著录', +'冒著稱' => '冒著称', +'冒著者' => '冒著者', +'冒著述' => '冒著述', +'寫著' => '写着', +'寫著書' => '写著书', +'寫著作' => '写著作', +'寫著名' => '写著名', +'寫著錄' => '写著录', +'寫著稱' => '写著称', +'寫著者' => '写著者', +'寫著述' => '写著述', +'涼著' => '凉着', +'涼著書' => '凉著书', +'涼著作' => '凉著作', +'涼著名' => '凉著名', +'涼著錄' => '凉著录', +'涼著稱' => '凉著称', +'涼著者' => '凉著者', +'涼著述' => '凉著述', +'憑藉' => '凭借', +'制著' => '制着', +'制著書' => '制著书', +'制著作' => '制著作', +'制著名' => '制著名', +'制著錄' => '制著录', +'制著稱' => '制著称', +'制著者' => '制著者', +'制著述' => '制著述', +'刻著' => '刻着', +'刻著書' => '刻著书', +'刻著作' => '刻著作', +'刻著名' => '刻著名', +'刻著錄' => '刻著录', +'刻著稱' => '刻著称', +'刻著者' => '刻著者', +'刻著述' => '刻著述', +'辦著' => '办着', +'辦著書' => '办著书', +'辦著作' => '办著作', +'辦著名' => '办著名', +'辦著錄' => '办著录', +'辦著稱' => '办著称', +'辦著者' => '办著者', +'辦著述' => '办著述', +'動著' => '动着', +'動著書' => '动著书', +'動著作' => '动著作', +'動著名' => '动著名', +'動著錄' => '动著录', +'動著稱' => '动著称', +'動著者' => '动著者', +'動著述' => '动著述', +'努力著' => '努力着', +'努力著書' => '努力著书', +'努力著作' => '努力著作', +'努力著名' => '努力著名', +'努力著錄' => '努力著录', +'努力著稱' => '努力著称', +'努力著者' => '努力著者', +'努力著述' => '努力著述', +'努著' => '努着', +'努著書' => '努著书', +'努著作' => '努著作', +'努著名' => '努著名', +'努著錄' => '努著录', +'努著稱' => '努著称', +'努著者' => '努著者', +'努著述' => '努著述', +'卓著' => '卓著', +'印著' => '印着', +'印著書' => '印著书', +'印著作' => '印著作', +'印著名' => '印著名', +'印著錄' => '印著录', +'印著稱' => '印著称', +'印著者' => '印著者', +'印著述' => '印著述', +'卷舌' => '卷舌', +'壓著' => '压着', +'壓著書' => '压著书', +'壓著作' => '压著作', +'壓著名' => '压著名', +'壓著錄' => '压著录', +'壓著稱' => '压著称', +'壓著者' => '压著者', +'壓著述' => '压著述', +'原著' => '原著', +'去著' => '去着', +'去著書' => '去著书', +'去著作' => '去著作', +'去著名' => '去著名', +'去著錄' => '去著录', +'去著稱' => '去著称', +'去著者' => '去著者', +'去著述' => '去著述', +'反反覆覆' => '反反复复', +'反覆' => '反复', +'受著' => '受着', +'受著書' => '受著书', +'受著作' => '受著作', +'受著名' => '受著名', +'受著錄' => '受著录', +'受著稱' => '受著称', +'受著者' => '受著者', +'受著述' => '受著述', +'變著' => '变着', +'變著書' => '变著书', +'變著作' => '变著作', +'變著名' => '变著名', +'變著錄' => '变著录', +'變著稱' => '变著称', +'變著者' => '变著者', +'變著述' => '变著述', +'叫著' => '叫着', +'叫著書' => '叫著书', +'叫著作' => '叫著作', +'叫著名' => '叫著名', +'叫著錄' => '叫著录', +'叫著稱' => '叫著称', +'叫著者' => '叫著者', +'叫著述' => '叫著述', +'可穿著' => '可穿著', +'叱吒' => '叱吒', +'吃衣著飯' => '吃衣著饭', +'合著' => '合著', +'名著' => '名著', +'向著' => '向着', +'向著書' => '向著书', +'向著作' => '向著作', +'向著名' => '向著名', +'向著錄' => '向著录', +'向著稱' => '向著称', +'向著者' => '向著者', +'向著述' => '向著述', +'含著' => '含着', +'含著書' => '含著书', +'含著作' => '含著作', +'含著名' => '含著名', +'含著錄' => '含著录', +'含著稱' => '含著称', +'含著者' => '含著者', +'含著述' => '含著述', +'聽著' => '听着', +'聽著書' => '听著书', +'聽著作' => '听著作', +'聽著名' => '听著名', +'聽著錄' => '听著录', +'聽著稱' => '听著称', +'聽著者' => '听著者', +'聽著述' => '听著述', +'吴其濬' => '吴其濬', +'吳其濬' => '吴其濬', +'吹著' => '吹着', +'吹著書' => '吹著书', +'吹著作' => '吹著作', +'吹著名' => '吹著名', +'吹著錄' => '吹著录', +'吹著稱' => '吹著称', +'吹著者' => '吹著者', +'吹著述' => '吹著述', +'味著' => '味着', +'味著書' => '味著书', +'味著作' => '味著作', +'味著名' => '味著名', +'味著錄' => '味著录', +'味著稱' => '味著称', +'味著者' => '味著者', +'味著述' => '味著述', +'呼幺喝六' => '呼幺喝六', +'響著' => '响着', +'響著書' => '响著书', +'響著作' => '响著作', +'響著名' => '响著名', +'響著錄' => '响著录', +'響著稱' => '响著称', +'響著者' => '响著者', +'響著述' => '响著述', +'哪吒' => '哪吒', +'哭著' => '哭着', +'哭著書' => '哭著书', +'哭著作' => '哭著作', +'哭著名' => '哭著名', +'哭著錄' => '哭著录', +'哭著稱' => '哭著称', +'哭著者' => '哭著者', +'哭著述' => '哭著述', +'唱著' => '唱着', +'唱著書' => '唱著书', +'唱著作' => '唱著作', +'唱著名' => '唱著名', +'唱著錄' => '唱著录', +'唱著稱' => '唱著称', +'唱著者' => '唱著者', +'唱著述' => '唱著述', +'喝著' => '喝着', +'喝著書' => '喝著书', +'喝著作' => '喝著作', +'喝著名' => '喝著名', +'喝著錄' => '喝著录', +'喝著稱' => '喝著称', +'喝著者' => '喝著者', +'喝著述' => '喝著述', +'嚷著' => '嚷着', +'嚷著書' => '嚷著书', +'嚷著作' => '嚷著作', +'嚷著名' => '嚷著名', +'嚷著錄' => '嚷著录', +'嚷著稱' => '嚷著称', +'嚷著者' => '嚷著者', +'嚷著述' => '嚷著述', +'回覆' => '回复', +'因著' => '因着', +'因著〈' => '因著〈', +'因著《' => '因著《', +'因著書' => '因著书', +'因著作' => '因著作', +'因著名' => '因著名', +'因著錄' => '因著录', +'因著稱' => '因著称', +'因著者' => '因著者', +'因著述' => '因著述', +'困著' => '困着', +'困著書' => '困著书', +'困著作' => '困著作', +'困著名' => '困著名', +'困著錄' => '困著录', +'困著稱' => '困著称', +'困著者' => '困著者', +'困著述' => '困著述', +'圍著' => '围着', +'圍著書' => '围著书', +'圍著作' => '围著作', +'圍著名' => '围著名', +'圍著錄' => '围著录', +'圍著稱' => '围著称', +'圍著者' => '围著者', +'圍著述' => '围著述', +'土著' => '土著', +'在著' => '在着', +'在著書' => '在著书', +'在著作' => '在著作', +'在著名' => '在著名', +'在著錄' => '在著录', +'在著稱' => '在著称', +'在著者' => '在著者', +'在著述' => '在著述', +'坐著' => '坐着', +'坐著書' => '坐著书', +'坐著作' => '坐著作', +'坐著名' => '坐著名', +'坐著錄' => '坐著录', +'坐著稱' => '坐著称', +'坐著者' => '坐著者', +'坐著述' => '坐著述', +'坤乾' => '坤乾', +'備著' => '备着', +'備著書' => '备著书', +'備著作' => '备著作', +'備著名' => '备著名', +'備著錄' => '备著录', +'備著稱' => '备著称', +'備著者' => '备著者', +'備著述' => '备著述', +'天道为乾' => '天道为乾', +'天道為乾' => '天道为乾', +'夾著' => '夹着', +'夾著書' => '夹著书', +'夾著作' => '夹著作', +'夾著名' => '夹著名', +'夾著錄' => '夹著录', +'夾著稱' => '夹著称', +'夾著者' => '夹著者', +'夾著述' => '夹著述', +'奧區' => '奧区', +'姓幺' => '姓幺', +'存摺' => '存摺', +'孤著' => '孤着', +'孤著書' => '孤著书', +'孤著作' => '孤著作', +'孤著名' => '孤著名', +'孤著錄' => '孤著录', +'孤著稱' => '孤著称', +'孤著者' => '孤著者', +'孤著述' => '孤著述', +'學著' => '学着', +'學著書' => '学著书', +'學著作' => '学著作', +'學著名' => '学著名', +'學著錄' => '学著录', +'學著稱' => '学著称', +'學著者' => '学著者', +'學著述' => '学著述', +'守著' => '守着', +'守著書' => '守著书', +'守著作' => '守著作', +'守著名' => '守著名', +'守著錄' => '守著录', +'守著稱' => '守著称', +'守著者' => '守著者', +'守著述' => '守著述', +'定著' => '定着', +'定著書' => '定著书', +'定著作' => '定著作', +'定著名' => '定著名', +'定著錄' => '定著录', +'定著稱' => '定著称', +'定著者' => '定著者', +'定著述' => '定著述', +'對著' => '对着', +'對著書' => '对著书', +'對著作' => '对著作', +'對著名' => '对著名', +'對著錄' => '对著录', +'對著稱' => '对著称', +'對著者' => '对著者', +'對著述' => '对著述', +'尋著' => '寻着', +'尋著書' => '寻著书', +'尋著作' => '寻著作', +'尋著名' => '寻著名', +'尋著錄' => '寻著录', +'尋著稱' => '寻著称', +'尋著者' => '寻著者', +'尋著述' => '寻著述', +'將軍抽車' => '将军抽車', +'尼乾陀' => '尼乾陀', +'展著' => '展着', +'展著書' => '展著书', +'展著作' => '展著作', +'展著名' => '展著名', +'展著錄' => '展著录', +'展著稱' => '展著称', +'展著者' => '展著者', +'展著述' => '展著述', +'巨著' => '巨著', +'帶著' => '带着', +'帶著書' => '带著书', +'帶著作' => '带著作', +'帶著名' => '带著名', +'帶著錄' => '带著录', +'帶著稱' => '带著称', +'帶著者' => '带著者', +'帶著述' => '带著述', +'幫著' => '帮着', +'幫著書' => '帮著书', +'幫著作' => '帮著作', +'幫著名' => '帮著名', +'幫著錄' => '帮著录', +'幫著稱' => '帮著称', +'幫著者' => '帮著者', +'幫著述' => '帮著述', +'乾泉水' => '干泉水', +'么二三' => '幺二三', +'幺二三' => '幺二三', +'么元' => '幺元', +'幺元' => '幺元', +'幺鳳' => '幺凤', +'么鳳' => '幺凤', +'么半群' => '幺半群', +'幺半群' => '幺半群', +'幺廝' => '幺厮', +'幺厮' => '幺厮', +'么叔' => '幺叔', +'幺叔' => '幺叔', +'么媽' => '幺妈', +'幺媽' => '幺妈', +'么妹' => '幺妹', +'幺妹' => '幺妹', +'么姓' => '幺姓', +'幺姓' => '幺姓', +'么姨' => '幺姨', +'幺姨' => '幺姨', +'么娘' => '幺娘', +'么孃' => '幺娘', +'幺娘' => '幺娘', +'幺孃' => '幺娘', +'幺小' => '幺小', +'么小' => '幺小', +'幺氏' => '幺氏', +'么氏' => '幺氏', +'么爸' => '幺爸', +'幺爸' => '幺爸', +'幺爹' => '幺爹', +'么爹' => '幺爹', +'么篇' => '幺篇', +'幺篇' => '幺篇', +'么舅' => '幺舅', +'幺舅' => '幺舅', +'么蛾子' => '幺蛾子', +'幺蛾子' => '幺蛾子', +'么謙' => '幺谦', +'幺謙' => '幺谦', +'幺麽' => '幺麽', +'么麼' => '幺麽', +'幺麽小丑' => '幺麽小丑', +'么麼小丑' => '幺麽小丑', +'應著' => '应着', +'應著書' => '应著书', +'應著作' => '应著作', +'應著名' => '应著名', +'應著錄' => '应著录', +'應著稱' => '应著称', +'應著者' => '应著者', +'應著述' => '应著述', +'康乾' => '康乾', +'康著' => '康着', +'康著書' => '康著书', +'康著作' => '康著作', +'康著名' => '康著名', +'康著錄' => '康著录', +'康著稱' => '康著称', +'康著者' => '康著者', +'康著述' => '康著述', +'開著' => '开着', +'開著書' => '开著书', +'開著作' => '开著作', +'開著名' => '开著名', +'開著錄' => '开著录', +'開著稱' => '开著称', +'開著者' => '开著者', +'開著述' => '开著述', +'張法乾' => '张法乾', +'當著' => '当着', +'當著書' => '当著书', +'當著作' => '当著作', +'當著名' => '当著名', +'當著錄' => '当著录', +'當著稱' => '当著称', +'當著者' => '当著者', +'當著述' => '当著述', +'彰明較著' => '彰明较著', +'待著' => '待着', +'待著書' => '待著书', +'待著作' => '待著作', +'待著名' => '待著名', +'待著錄' => '待著录', +'待著稱' => '待著称', +'待著者' => '待著者', +'待著述' => '待著述', +'得著' => '得着', +'得著書' => '得著书', +'得著作' => '得著作', +'得著名' => '得著名', +'得著錄' => '得著录', +'得著稱' => '得著称', +'得著者' => '得著者', +'得著述' => '得著述', +'循著' => '循着', +'循著書' => '循著书', +'循著作' => '循著作', +'循著名' => '循著名', +'循著錄' => '循著录', +'循著稱' => '循著称', +'循著者' => '循著者', +'循著述' => '循著述', +'心著' => '心着', +'心著書' => '心著书', +'心著作' => '心著作', +'心著名' => '心著名', +'心著錄' => '心著录', +'心著稱' => '心著称', +'心著者' => '心著者', +'心著述' => '心著述', +'忍著' => '忍着', +'忍著書' => '忍著书', +'忍著作' => '忍著作', +'忍著名' => '忍著名', +'忍著錄' => '忍著录', +'忍著稱' => '忍著称', +'忍著者' => '忍著者', +'忍著述' => '忍著述', +'志著' => '志着', +'志著書' => '志著书', +'志著作' => '志著作', +'志著名' => '志著名', +'志著錄' => '志著录', +'志著稱' => '志著称', +'志著者' => '志著者', +'志著述' => '志著述', +'忙著' => '忙着', +'忙著書' => '忙著书', +'忙著作' => '忙著作', +'忙著名' => '忙著名', +'忙著錄' => '忙著录', +'忙著稱' => '忙著称', +'忙著者' => '忙著者', +'忙著述' => '忙著述', +'懷著' => '怀着', +'懷著書' => '怀著书', +'懷著作' => '怀著作', +'懷著名' => '怀著名', +'懷著錄' => '怀著录', +'懷著稱' => '怀著称', +'懷著者' => '怀著者', +'懷著述' => '怀著述', +'急著' => '急着', +'急著書' => '急著书', +'急著作' => '急著作', +'急著名' => '急著名', +'急著錄' => '急著录', +'急著稱' => '急著称', +'急著者' => '急著者', +'急著述' => '急著述', +'性著' => '性着', +'性著書' => '性著书', +'性著作' => '性著作', +'性著名' => '性著名', +'性著錄' => '性著录', +'性著稱' => '性著称', +'性著者' => '性著者', +'性著述' => '性著述', +'戀著' => '恋着', +'戀著書' => '恋著书', +'戀著作' => '恋著作', +'戀著名' => '恋著名', +'戀著錄' => '恋著录', +'戀著稱' => '恋著称', +'戀著者' => '恋著者', +'戀著述' => '恋著述', +'恩威並著' => '恩威并著', +'悠著' => '悠着', +'悠著書' => '悠著书', +'悠著作' => '悠著作', +'悠著名' => '悠著名', +'悠著錄' => '悠著录', +'悠著稱' => '悠著称', +'悠著者' => '悠著者', +'悠著述' => '悠著述', +'慣著' => '惯着', +'慣著書' => '惯著书', +'慣著作' => '惯著作', +'慣著名' => '惯著名', +'慣著錄' => '惯著录', +'慣著稱' => '惯著称', +'慣著者' => '惯著者', +'慣著述' => '惯著述', +'想著' => '想着', +'想著書' => '想著书', +'想著作' => '想著作', +'想著名' => '想著名', +'想著錄' => '想著录', +'想著稱' => '想著称', +'想著者' => '想著者', +'想著述' => '想著述', +'成效顯著' => '成效显著', +'成績顯著' => '成绩显著', +'戰著' => '战着', +'戰著書' => '战著书', +'戰著作' => '战著作', +'戰著名' => '战著名', +'戰著錄' => '战著录', +'戰著稱' => '战著称', +'戰著者' => '战著者', +'戰著述' => '战著述', +'戴著' => '戴着', +'戴著書' => '戴著书', +'戴著作' => '戴著作', +'戴著名' => '戴著名', +'戴著錄' => '戴著录', +'戴著稱' => '戴著称', +'戴著者' => '戴著者', +'戴著述' => '戴著述', +'扎著' => '扎着', +'扎著書' => '扎著书', +'扎著作' => '扎著作', +'扎著名' => '扎著名', +'扎著錄' => '扎著录', +'扎著稱' => '扎著称', +'扎著者' => '扎著者', +'扎著述' => '扎著述', +'打著' => '打着', +'打著書' => '打著书', +'打著作' => '打著作', +'打著名' => '打著名', +'打著錄' => '打著录', +'打著稱' => '打著称', +'打著者' => '打著者', +'打著述' => '打著述', +'扛著' => '扛着', +'扛著書' => '扛著书', +'扛著作' => '扛著作', +'扛著名' => '扛著名', +'扛著錄' => '扛著录', +'扛著稱' => '扛著称', +'扛著者' => '扛著者', +'扛著述' => '扛著述', +'執著' => '执著', +'找不著' => '找不着', +'找不著書' => '找不著书', +'找不著作' => '找不著作', +'找不著名' => '找不著名', +'找不著錄' => '找不著录', +'找不著稱' => '找不著称', +'找不著者' => '找不著者', +'找不著述' => '找不著述', +'抓著' => '抓着', +'抓著書' => '抓著书', +'抓著作' => '抓著作', +'抓著名' => '抓著名', +'抓著錄' => '抓著录', +'抓著稱' => '抓著称', +'抓著者' => '抓著者', +'抓著述' => '抓著述', +'護著' => '护着', +'護著書' => '护著书', +'護著作' => '护著作', +'護著名' => '护著名', +'護著錄' => '护著录', +'護著稱' => '护著称', +'護著者' => '护著者', +'護著述' => '护著述', +'披著' => '披着', +'披著書' => '披著书', +'披著作' => '披著作', +'披著名' => '披著名', +'披著錄' => '披著录', +'披著稱' => '披著称', +'披著者' => '披著者', +'披著述' => '披著述', +'抬著' => '抬着', +'抬著書' => '抬著书', +'抬著作' => '抬著作', +'抬著名' => '抬著名', +'抬著錄' => '抬著录', +'抬著稱' => '抬著称', +'抬著者' => '抬著者', +'抬著述' => '抬著述', +'抱著' => '抱着', +'抱著書' => '抱著书', +'抱著作' => '抱著作', +'抱著名' => '抱著名', +'抱著錄' => '抱著录', +'抱著稱' => '抱著称', +'抱著者' => '抱著者', +'抱著述' => '抱著述', +'拉著' => '拉着', +'拉著書' => '拉著书', +'拉著作' => '拉著作', +'拉著名' => '拉著名', +'拉著錄' => '拉著录', +'拉著稱' => '拉著称', +'拉著者' => '拉著者', +'拉著述' => '拉著述', +'拉鍊' => '拉链', +'拎著' => '拎着', +'拎著書' => '拎著书', +'拎著作' => '拎著作', +'拎著名' => '拎著名', +'拎著錄' => '拎著录', +'拎著稱' => '拎著称', +'拎著者' => '拎著者', +'拎著述' => '拎著述', +'拖著' => '拖着', +'拖著書' => '拖著书', +'拖著作' => '拖著作', +'拖著名' => '拖著名', +'拖著錄' => '拖著录', +'拖著稱' => '拖著称', +'拖著者' => '拖著者', +'拖著述' => '拖著述', +'拙著' => '拙著', +'拚命' => '拚命', +'拚搏' => '拚搏', +'拚死' => '拚死', +'拼著' => '拼着', +'拼著書' => '拼著书', +'拼著作' => '拼著作', +'拼著名' => '拼著名', +'拼著錄' => '拼著录', +'拼著稱' => '拼著称', +'拼著者' => '拼著者', +'拼著述' => '拼著述', +'拿著' => '拿着', +'拿著書' => '拿著书', +'拿著作' => '拿著作', +'拿著名' => '拿著名', +'拿著錄' => '拿著录', +'拿著稱' => '拿著称', +'拿著者' => '拿著者', +'拿著述' => '拿著述', +'持著' => '持着', +'持著書' => '持著书', +'持著作' => '持著作', +'持著名' => '持著名', +'持著錄' => '持著录', +'持著稱' => '持著称', +'持著者' => '持著者', +'持著述' => '持著述', +'挑著' => '挑着', +'挑著書' => '挑著书', +'挑著作' => '挑著作', +'挑著名' => '挑著名', +'挑著錄' => '挑著录', +'挑著稱' => '挑著称', +'挑著者' => '挑著者', +'挑著述' => '挑著述', +'擋著' => '挡着', +'擋著書' => '挡著书', +'擋著作' => '挡著作', +'擋著名' => '挡著名', +'擋著錄' => '挡著录', +'擋著稱' => '挡著称', +'擋著者' => '挡著者', +'擋著述' => '挡著述', +'掙著' => '挣着', +'掙著書' => '挣著书', +'掙著作' => '挣著作', +'掙著名' => '挣著名', +'掙著錄' => '挣著录', +'掙著稱' => '挣著称', +'掙著者' => '挣著者', +'掙著述' => '挣著述', +'揮著' => '挥着', +'揮著書' => '挥著书', +'揮著作' => '挥著作', +'揮著名' => '挥著名', +'揮著錄' => '挥著录', +'揮著稱' => '挥著称', +'揮著者' => '挥著者', +'揮著述' => '挥著述', +'挨著' => '挨着', +'挨著書' => '挨著书', +'挨著作' => '挨著作', +'挨著名' => '挨著名', +'挨著錄' => '挨著录', +'挨著稱' => '挨著称', +'挨著者' => '挨著者', +'挨著述' => '挨著述', +'捆著' => '捆着', +'捆著書' => '捆著书', +'捆著作' => '捆著作', +'捆著名' => '捆著名', +'捆著錄' => '捆著录', +'捆著稱' => '捆著称', +'捆著者' => '捆著者', +'捆著述' => '捆著述', +'據著' => '据着', +'據著書' => '据著书', +'據著作' => '据著作', +'據著名' => '据著名', +'據著錄' => '据著录', +'據著稱' => '据著称', +'據著者' => '据著者', +'據著述' => '据著述', +'掖著' => '掖着', +'掖著書' => '掖著书', +'掖著作' => '掖著作', +'掖著名' => '掖著名', +'掖著錄' => '掖著录', +'掖著稱' => '掖著称', +'掖著者' => '掖著者', +'掖著述' => '掖著述', +'接著' => '接着', +'接著書' => '接著书', +'接著作' => '接著作', +'接著名' => '接著名', +'接著錄' => '接著录', +'接著稱' => '接著称', +'接著者' => '接著者', +'接著述' => '接著述', +'揉著' => '揉着', +'揉著書' => '揉著书', +'揉著作' => '揉著作', +'揉著名' => '揉著名', +'揉著錄' => '揉著录', +'揉著稱' => '揉著称', +'揉著者' => '揉著者', +'揉著述' => '揉著述', +'提著' => '提着', +'提著書' => '提著书', +'提著作' => '提著作', +'提著名' => '提著名', +'提著錄' => '提著录', +'提著稱' => '提著称', +'提著者' => '提著者', +'提著述' => '提著述', +'摟著' => '搂着', +'摟著書' => '搂著书', +'摟著作' => '搂著作', +'摟著名' => '搂著名', +'摟著錄' => '搂著录', +'摟著稱' => '搂著称', +'摟著者' => '搂著者', +'摟著述' => '搂著述', +'擺著' => '摆着', +'擺著書' => '摆著书', +'擺著作' => '摆著作', +'擺著名' => '摆著名', +'擺著錄' => '摆著录', +'擺著稱' => '摆著称', +'擺著者' => '摆著者', +'擺著述' => '摆著述', +'摺疊' => '摺叠', +'撰著' => '撰著', +'撼著' => '撼着', +'撼著書' => '撼著书', +'撼著作' => '撼著作', +'撼著名' => '撼著名', +'撼著錄' => '撼著录', +'撼著稱' => '撼著称', +'撼著者' => '撼著者', +'撼著述' => '撼著述', +'效果顯著' => '效果显著', +'敞著' => '敞着', +'敞著書' => '敞著书', +'敞著作' => '敞著作', +'敞著名' => '敞著名', +'敞著錄' => '敞著录', +'敞著稱' => '敞著称', +'敞著者' => '敞著者', +'敞著述' => '敞著述', +'數著' => '数着', +'數著書' => '数著书', +'數著作' => '数著作', +'數著名' => '数著名', +'數著錄' => '数著录', +'數著稱' => '数著称', +'數著者' => '数著者', +'數著述' => '数著述', +'斗著' => '斗着', +'斗著書' => '斗著书', +'斗著作' => '斗著作', +'斗著名' => '斗著名', +'斗著錄' => '斗著录', +'斗著稱' => '斗著称', +'斗著者' => '斗著者', +'斗著述' => '斗著述', +'斥著' => '斥着', +'斥著書' => '斥著书', +'斥著作' => '斥著作', +'斥著名' => '斥著名', +'斥著錄' => '斥著录', +'斥著稱' => '斥著称', +'斥著者' => '斥著者', +'斥著述' => '斥著述', +'新著' => '新著', +'新著龍虎門' => '新著龙虎门', +'於乎' => '於乎', +'於夫罗' => '於夫罗', +'於夫羅' => '於夫罗', +'於姓' => '於姓', +'於崇文' => '於崇文', +'於戲' => '於戏', +'於梨華' => '於梨华', +'於梨华' => '於梨华', +'於氏' => '於氏', +'於潛縣' => '於潜县', +'於潜县' => '於潜县', +'於祥玉' => '於祥玉', +'於菟' => '於菟', +'於賢德' => '於贤德', +'旋乾轉坤' => '旋乾转坤', +'曠若發矇' => '旷若发矇', +'昂著' => '昂着', +'昂著書' => '昂著书', +'昂著作' => '昂著作', +'昂著名' => '昂著名', +'昂著錄' => '昂著录', +'昂著稱' => '昂著称', +'昂著者' => '昂著者', +'昂著述' => '昂著述', +'易·乾' => '易·乾', +'易經·乾' => '易经·乾', +'易经·乾' => '易经·乾', +'易經乾' => '易经乾', +'易经乾' => '易经乾', +'映著' => '映着', +'映著書' => '映著书', +'映著作' => '映著作', +'映著名' => '映著名', +'映著錄' => '映著录', +'映著稱' => '映著称', +'映著者' => '映著者', +'映著述' => '映著述', +'昭著' => '昭著', +'顯著' => '显著', +'顯著地' => '显著地', +'顯著地位' => '显著地位', +'顯著性' => '显著性', +'顯著成績' => '显著成绩', +'顯著效果' => '显著效果', +'顯著特點' => '显著特点', +'晃著' => '晃着', +'晃著書' => '晃著书', +'晃著作' => '晃著作', +'晃著名' => '晃著名', +'晃著錄' => '晃著录', +'晃著稱' => '晃著称', +'晃著者' => '晃著者', +'晃著述' => '晃著述', +'暗著' => '暗着', +'暗著書' => '暗著书', +'暗著作' => '暗著作', +'暗著名' => '暗著名', +'暗著錄' => '暗著录', +'暗著稱' => '暗著称', +'暗著者' => '暗著者', +'暗著述' => '暗著述', +'有著' => '有着', +'有著書' => '有著书', +'有著作' => '有著作', +'有著名' => '有著名', +'有著錄' => '有著录', +'有著稱' => '有著称', +'有著者' => '有著者', +'有著述' => '有著述', +'望著' => '望着', +'望著書' => '望著书', +'望著作' => '望著作', +'望著名' => '望著名', +'望著錄' => '望著录', +'望著稱' => '望著称', +'望著者' => '望著者', +'望著述' => '望著述', +'朝乾夕惕' => '朝乾夕惕', +'朝著' => '朝着', +'朝著書' => '朝著书', +'朝著作' => '朝著作', +'朝著名' => '朝著名', +'朝著錄' => '朝著录', +'朝著稱' => '朝著称', +'朝著者' => '朝著者', +'朝著述' => '朝著述', +'本著' => '本着', +'本著書' => '本著书', +'本著作' => '本著作', +'本著名' => '本著名', +'本著錄' => '本著录', +'本著稱' => '本著称', +'本著者' => '本著者', +'本著述' => '本著述', +'殺著' => '杀着', +'殺著書' => '杀著书', +'殺著作' => '杀著作', +'殺著名' => '杀著名', +'殺著錄' => '杀著录', +'殺著稱' => '杀著称', +'殺著者' => '杀著者', +'殺著述' => '杀著述', +'雜著' => '杂着', +'雜著書' => '杂著书', +'雜著作' => '杂著作', +'雜著名' => '杂著名', +'雜著錄' => '杂著录', +'雜著稱' => '杂著称', +'雜著者' => '杂著者', +'雜著述' => '杂著述', +'李乾德' => '李乾德', +'李澤鉅' => '李泽钜', +'來著' => '来着', +'來著書' => '来著书', +'來著作' => '来著作', +'來著名' => '来著名', +'來著錄' => '来著录', +'來著稱' => '来著称', +'來著者' => '来著者', +'來著述' => '来著述', +'楊幺' => '杨幺', +'枕著' => '枕着', +'枕著書' => '枕著书', +'枕著作' => '枕著作', +'枕著名' => '枕著名', +'枕著錄' => '枕著录', +'枕著稱' => '枕著称', +'枕著者' => '枕著者', +'枕著述' => '枕著述', +'柳詒徵' => '柳诒徵', +'柳诒徵' => '柳诒徵', +'夢著' => '梦着', +'夢著書' => '梦著书', +'夢著作' => '梦著作', +'夢著名' => '梦著名', +'夢著錄' => '梦著录', +'夢著稱' => '梦著称', +'夢著者' => '梦著者', +'夢著述' => '梦著述', +'梳著' => '梳着', +'梳著書' => '梳著书', +'梳著作' => '梳著作', +'梳著名' => '梳著名', +'梳著錄' => '梳著录', +'梳著稱' => '梳著称', +'梳著者' => '梳著者', +'梳著述' => '梳著述', +'樊於期' => '樊於期', +'比較顯著' => '比较显著', +'氆氌' => '氆氌', +'求著' => '求着', +'求著書' => '求著书', +'求著作' => '求著作', +'求著名' => '求著名', +'求著錄' => '求著录', +'求著稱' => '求著称', +'求著者' => '求著者', +'求著述' => '求著述', +'沈沒' => '沉没', +'沉著' => '沉着', +'沈積' => '沉积', +'沈船' => '沉船', +'沉著書' => '沉著书', +'沉著作' => '沉著作', +'沉著名' => '沉著名', +'沉著錄' => '沉著录', +'沉著稱' => '沉著称', +'沉著者' => '沉著者', +'沉著述' => '沉著述', +'沈默' => '沉默', +'沿著' => '沿着', +'沿著書' => '沿著书', +'沿著作' => '沿著作', +'沿著名' => '沿著名', +'沿著錄' => '沿著录', +'沿著稱' => '沿著称', +'沿著者' => '沿著者', +'沿著述' => '沿著述', +'洗鍊' => '洗练', +'活著' => '活着', +'活著書' => '活著书', +'活著作' => '活著作', +'活著名' => '活著名', +'活著錄' => '活著录', +'活著稱' => '活著称', +'活著者' => '活著者', +'活著述' => '活著述', +'流著' => '流着', +'流著書' => '流著书', +'流著作' => '流著作', +'流著名' => '流著名', +'流著錄' => '流著录', +'流著稱' => '流著称', +'流著者' => '流著者', +'流著述' => '流著述', +'浮著' => '浮着', +'浮著書' => '浮著书', +'浮著作' => '浮著作', +'浮著名' => '浮著名', +'浮著錄' => '浮著录', +'浮著稱' => '浮著称', +'浮著者' => '浮著者', +'浮著述' => '浮著述', +'潤著' => '润着', +'潤著書' => '润著书', +'潤著作' => '润著作', +'潤著名' => '润著名', +'潤著錄' => '润著录', +'潤著稱' => '润著称', +'潤著者' => '润著者', +'潤著述' => '润著述', +'涵著' => '涵着', +'涵著書' => '涵著书', +'涵著作' => '涵著作', +'涵著名' => '涵著名', +'涵著錄' => '涵著录', +'涵著稱' => '涵著称', +'涵著者' => '涵著者', +'涵著述' => '涵著述', +'渴著' => '渴着', +'渴著書' => '渴著书', +'渴著作' => '渴著作', +'渴著名' => '渴著名', +'渴著錄' => '渴著录', +'渴著稱' => '渴著称', +'渴著者' => '渴著者', +'渴著述' => '渴著述', +'溢著' => '溢着', +'溢著書' => '溢著书', +'溢著作' => '溢著作', +'溢著名' => '溢著名', +'溢著錄' => '溢著录', +'溢著稱' => '溢著称', +'溢著者' => '溢著者', +'溢著述' => '溢著述', +'演著' => '演着', +'演著書' => '演著书', +'演著作' => '演著作', +'演著名' => '演著名', +'演著錄' => '演著录', +'演著稱' => '演著称', +'演著者' => '演著者', +'演著述' => '演著述', +'漫著' => '漫着', +'漫著書' => '漫著书', +'漫著作' => '漫著作', +'漫著名' => '漫著名', +'漫著錄' => '漫著录', +'漫著稱' => '漫著称', +'漫著者' => '漫著者', +'漫著述' => '漫著述', +'點著' => '点着', +'點著作' => '点著作', +'點著名' => '点著名', +'點著錄' => '点著录', +'點著稱' => '点著称', +'點著者' => '点著者', +'點著述' => '点著述', +'燒著' => '烧着', +'燒著作' => '烧著作', +'燒著名' => '烧著名', +'燒著錄' => '烧著录', +'燒著稱' => '烧著称', +'燒著者' => '烧著者', +'燒著述' => '烧著述', +'照著' => '照着', +'照著書' => '照著书', +'照著作' => '照著作', +'照著名' => '照著名', +'照著錄' => '照著录', +'照著稱' => '照著称', +'照著者' => '照著者', +'照著述' => '照著述', +'愛著' => '爱着', +'愛著書' => '爱著书', +'愛著作' => '爱著作', +'愛著名' => '爱著名', +'愛著錄' => '爱著录', +'愛著稱' => '爱著称', +'愛著者' => '爱著者', +'愛著述' => '爱著述', +'牽著' => '牵着', +'牽著書' => '牵著书', +'牽著作' => '牵著作', +'牽著名' => '牵著名', +'牽著錄' => '牵著录', +'牽著稱' => '牵著称', +'牽著者' => '牵著者', +'牽著述' => '牵著述', +'犯不著' => '犯不着', +'獨著' => '独着', +'獨著書' => '独著书', +'獨著作' => '独著作', +'獨著名' => '独著名', +'獨著錄' => '独著录', +'獨著稱' => '独著称', +'獨著者' => '独著者', +'獨著述' => '独著述', +'猜著' => '猜着', +'猜著書' => '猜着书', +'猜著作' => '猜著作', +'猜著名' => '猜著名', +'猜著錄' => '猜著录', +'猜著稱' => '猜著称', +'猜著者' => '猜著者', +'猜著述' => '猜著述', +'甜著' => '甜着', +'甜著書' => '甜著书', +'甜著作' => '甜著作', +'甜著名' => '甜著名', +'甜著錄' => '甜著录', +'甜著稱' => '甜著称', +'甜著者' => '甜著者', +'甜著述' => '甜著述', +'用不著' => '用不着', +'用不著書' => '用不着书', +'用不著作' => '用不著作', +'用不著名' => '用不著名', +'用不著錄' => '用不著录', +'用不著稱' => '用不著称', +'用不著者' => '用不著者', +'用不著述' => '用不著述', +'用著' => '用着', +'用著書' => '用著书', +'用著作' => '用著作', +'用著名' => '用著名', +'用著錄' => '用著录', +'用著稱' => '用著称', +'用著者' => '用著者', +'用著述' => '用著述', +'留著' => '留着', +'留著書' => '留着书', +'留著作' => '留著作', +'留著名' => '留著名', +'留著錄' => '留著录', +'留著稱' => '留著称', +'留著者' => '留著者', +'留著述' => '留著述', +'疑著' => '疑着', +'疑著書' => '疑著书', +'疑著作' => '疑著作', +'疑著名' => '疑著名', +'疑著錄' => '疑著录', +'疑著稱' => '疑著称', +'疑著者' => '疑著者', +'疑著述' => '疑著述', +'療效顯著' => '疗效显著', +'癥瘕' => '癥瘕', +'皺著' => '皱着', +'皺著書' => '皱著书', +'皺著作' => '皱著作', +'皺著名' => '皱著名', +'皺著錄' => '皱著录', +'皺著稱' => '皱著称', +'皺著者' => '皱著者', +'皺著述' => '皱著述', +'盛著' => '盛着', +'盛著書' => '盛著书', +'盛著作' => '盛著作', +'盛著名' => '盛著名', +'盛著錄' => '盛著录', +'盛著稱' => '盛著称', +'盛著者' => '盛著者', +'盛著述' => '盛著述', +'盯著' => '盯着', +'盯著書' => '盯着书', +'盯著作' => '盯著作', +'盯著名' => '盯著名', +'盯著錄' => '盯著录', +'盯著稱' => '盯著称', +'盯著者' => '盯著者', +'盯著述' => '盯著述', +'盾著' => '盾着', +'盾著書' => '盾著书', +'盾著作' => '盾著作', +'盾著名' => '盾著名', +'盾著錄' => '盾著录', +'盾著稱' => '盾著称', +'盾著者' => '盾著者', +'盾著述' => '盾著述', +'看著' => '看着', +'看著書' => '看着书', +'看著作' => '看著作', +'看著名' => '看著名', +'看著錄' => '看著录', +'看著稱' => '看著称', +'看著者' => '看著者', +'看著述' => '看著述', +'著業' => '着业', +'著絲' => '着丝', +'著么' => '着么', +'著人' => '着人', +'著什么急' => '着什么急', +'著他' => '着他', +'著令' => '着令', +'著位' => '着位', +'著體' => '着体', +'著你' => '着你', +'著便' => '着便', +'著涼' => '着凉', +'著力' => '着力', +'著勁' => '着劲', +'著號' => '着号', +'著呢' => '着呢', +'著哩' => '着哩', +'著地' => '着地', +'著墨' => '着墨', +'著聲' => '着声', +'著處' => '着处', +'著她' => '着她', +'著妳' => '着妳', +'著姓' => '着姓', +'著它' => '着它', +'著定' => '着定', +'著實' => '着实', +'著己' => '着己', +'著帳' => '着帐', +'著床' => '着床', +'著庸' => '着庸', +'著式' => '着式', +'著錄' => '着录', +'著心' => '着心', +'著志' => '着志', +'著忙' => '着忙', +'著急' => '着急', +'著惱' => '着恼', +'著驚' => '着惊', +'著想' => '着想', +'著意' => '着意', +'著慌' => '着慌', +'著我' => '着我', +'著手' => '着手', +'著抹' => '着抹', +'著摸' => '着摸', +'著撰' => '着撰', +'著數' => '着数', +'著明' => '着明', +'著末' => '着末', +'著極' => '着极', +'著格' => '着格', +'著棋' => '着棋', +'著槁' => '着槁', +'著氣' => '着气', +'著法' => '着法', +'著淺' => '着浅', +'著火' => '着火', +'著然' => '着然', +'著甚' => '着甚', +'著生' => '着生', +'著疑' => '着疑', +'著白' => '着白', +'著相' => '着相', +'著眼' => '着眼', +'著著' => '着着', +'著祂' => '着祂', +'著積' => '着积', +'著稿' => '着稿', +'著筆' => '着笔', +'著籍' => '着籍', +'著緊' => '着紧', +'著緑' => '着緑', +'著絆' => '着绊', +'著績' => '着绩', +'著緋' => '着绯', +'著綠' => '着绿', +'著肉' => '着肉', +'著腳' => '着脚', +'著艦' => '着舰', +'著色' => '着色', +'著節' => '着节', +'著花' => '着花', +'著莫' => '着莫', +'著落' => '着落', +'著藁' => '着藁', +'著衣' => '着衣', +'著裝' => '着装', +'著要' => '着要', +'著警' => '着警', +'著趣' => '着趣', +'著邊' => '着边', +'著迷' => '着迷', +'著跡' => '着迹', +'著重' => '着重', +'著録' => '着録', +'著聞' => '着闻', +'著陸' => '着陆', +'著雝' => '着雝', +'著鞭' => '着鞭', +'著題' => '着题', +'著魔' => '着魔', +'睡不著' => '睡不着', +'睡不著書' => '睡不著书', +'睡不著作' => '睡不著作', +'睡不著名' => '睡不著名', +'睡不著錄' => '睡不著录', +'睡不著稱' => '睡不著称', +'睡不著者' => '睡不著者', +'睡不著述' => '睡不著述', +'睡著' => '睡着', +'睡著書' => '睡著书', +'睡著作' => '睡著作', +'睡著名' => '睡著名', +'睡著錄' => '睡著录', +'睡著稱' => '睡著称', +'睡著者' => '睡著者', +'睡著述' => '睡著述', +'睹微知著' => '睹微知著', +'睪丸' => '睾丸', +'瞞著' => '瞒着', +'瞞著書' => '瞒著书', +'瞞著作' => '瞒著作', +'瞞著名' => '瞒著名', +'瞞著錄' => '瞒著录', +'瞞著稱' => '瞒著称', +'瞞著者' => '瞒著者', +'瞞著述' => '瞒著述', +'瞪著' => '瞪着', +'瞪著書' => '瞪著书', +'瞪著作' => '瞪著作', +'瞪著名' => '瞪著名', +'瞪著錄' => '瞪著录', +'瞪著稱' => '瞪著称', +'瞪著者' => '瞪著者', +'瞪著述' => '瞪著述', +'瞭望' => '瞭望', +'石碁镇' => '石碁镇', +'石碁鎮' => '石碁镇', +'福著' => '福着', +'福著書' => '福著书', +'福著作' => '福著作', +'福著名' => '福著名', +'福著錄' => '福著录', +'福著稱' => '福著称', +'福著者' => '福著者', +'福著述' => '福著述', +'穀梁' => '穀梁', +'空著' => '空着', +'空著書' => '空著书', +'空著作' => '空著作', +'空著名' => '空著名', +'空著錄' => '空著录', +'空著稱' => '空著称', +'空著者' => '空著者', +'空著述' => '空著述', +'穿著' => '穿着', +'穿著書' => '穿著书', +'穿著作' => '穿著作', +'穿著名' => '穿著名', +'穿著錄' => '穿著录', +'穿著稱' => '穿著称', +'穿著者' => '穿著者', +'穿著述' => '穿著述', +'豎著' => '竖着', +'豎著書' => '竖著书', +'豎著作' => '竖著作', +'豎著名' => '竖著名', +'豎著錄' => '竖著录', +'豎著稱' => '竖著称', +'豎著者' => '竖著者', +'豎著述' => '竖著述', +'站著' => '站着', +'站著書' => '站著书', +'站著作' => '站著作', +'站著名' => '站著名', +'站著錄' => '站著录', +'站著稱' => '站著称', +'站著者' => '站著者', +'站著述' => '站著述', +'笑著' => '笑着', +'笑著書' => '笑著书', +'笑著作' => '笑著作', +'笑著名' => '笑著名', +'笑著錄' => '笑著录', +'笑著稱' => '笑著称', +'笑著者' => '笑著者', +'笑著述' => '笑著述', +'答覆' => '答复', +'管著' => '管着', +'管著書' => '管著书', +'管著作' => '管著作', +'管著名' => '管著名', +'管著錄' => '管著录', +'管著稱' => '管著称', +'管著者' => '管著者', +'管著述' => '管著述', +'綁著' => '绑着', +'綁著書' => '绑著书', +'綁著作' => '绑著作', +'綁著名' => '绑著名', +'綁著錄' => '绑著录', +'綁著稱' => '绑著称', +'綁著者' => '绑著者', +'綁著述' => '绑著述', +'繞著' => '绕着', +'繞著書' => '绕著书', +'繞著作' => '绕著作', +'繞著名' => '绕著名', +'繞著錄' => '绕著录', +'繞著稱' => '绕著称', +'繞著者' => '绕著者', +'繞著述' => '绕著述', +'編著' => '编著', +'纏著' => '缠着', +'纏著書' => '缠著书', +'纏著作' => '缠著作', +'纏著名' => '缠著名', +'纏著錄' => '缠著录', +'纏著稱' => '缠著称', +'纏著者' => '缠著者', +'纏著述' => '缠著述', +'罩著' => '罩着', +'罩著書' => '罩著书', +'罩著作' => '罩著作', +'罩著名' => '罩著名', +'罩著錄' => '罩著录', +'罩著稱' => '罩著称', +'罩著者' => '罩著者', +'罩著述' => '罩著述', +'美著' => '美着', +'美著書' => '美著书', +'美著作' => '美著作', +'美著名' => '美著名', +'美著錄' => '美著录', +'美著稱' => '美著称', +'美著者' => '美著者', +'美著述' => '美著述', +'耀著' => '耀着', +'耀著書' => '耀著书', +'耀著作' => '耀著作', +'耀著名' => '耀著名', +'耀著錄' => '耀著录', +'耀著稱' => '耀著称', +'耀著者' => '耀著者', +'耀著述' => '耀著述', +'老幺' => '老幺', +'考著' => '考着', +'考著書' => '考著书', +'考著作' => '考著作', +'考著名' => '考著名', +'考著錄' => '考著录', +'考著稱' => '考著称', +'考著者' => '考著者', +'考著述' => '考著述', +'肘手鍊足' => '肘手链足', +'背著' => '背着', +'背著書' => '背著书', +'背著作' => '背著作', +'背著名' => '背著名', +'背著錄' => '背著录', +'背著稱' => '背著称', +'背著者' => '背著者', +'背著述' => '背著述', +'膠著' => '胶着', +'膠著書' => '胶著书', +'膠著作' => '胶著作', +'膠著名' => '胶著名', +'膠著錄' => '胶著录', +'膠著稱' => '胶著称', +'膠著者' => '胶著者', +'膠著述' => '胶著述', +'藝著' => '艺着', +'藝著書' => '艺著书', +'藝著作' => '艺著作', +'藝著名' => '艺著名', +'藝著錄' => '艺著录', +'藝著稱' => '艺著称', +'藝著者' => '艺著者', +'藝著述' => '艺著述', +'苦著' => '苦着', +'苦著書' => '苦著书', +'苦著作' => '苦著作', +'苦著名' => '苦著名', +'苦著錄' => '苦著录', +'苦著稱' => '苦著称', +'苦著者' => '苦著者', +'苦著述' => '苦著述', +'獲著' => '获着', +'獲著書' => '获著书', +'獲著作' => '获著作', +'獲著名' => '获著名', +'獲著錄' => '获著录', +'獲著稱' => '获著称', +'獲著者' => '获著者', +'獲著述' => '获著述', +'蕭乾' => '萧乾', +'萧乾' => '萧乾', +'落著' => '落着', +'落著書' => '落著书', +'落著作' => '落著作', +'落著名' => '落著名', +'落著錄' => '落著录', +'落著稱' => '落著称', +'落著者' => '落著者', +'落著述' => '落著述', +'著書' => '著书', +'著書立說' => '著书立说', +'著作' => '著作', +'著名' => '著名', +'著錄規則' => '著录规则', +'著文' => '著文', +'著有' => '著有', +'著稱' => '著称', +'著者' => '著者', +'著身' => '著身', +'著述' => '著述', +'蒙著' => '蒙着', +'蒙著書' => '蒙著书', +'蒙著作' => '蒙著作', +'蒙著名' => '蒙著名', +'蒙著錄' => '蒙著录', +'蒙著稱' => '蒙著称', +'蒙著者' => '蒙著者', +'蒙著述' => '蒙著述', +'藏著' => '藏着', +'藏著書' => '藏著书', +'藏著作' => '藏著作', +'藏著名' => '藏著名', +'藏著錄' => '藏著录', +'藏著稱' => '藏著称', +'藏著者' => '藏著者', +'藏著述' => '藏著述', +'蘸著' => '蘸着', +'蘸著書' => '蘸著书', +'蘸著作' => '蘸著作', +'蘸著名' => '蘸著名', +'蘸著錄' => '蘸著录', +'蘸著稱' => '蘸著称', +'蘸著者' => '蘸著者', +'蘸著述' => '蘸著述', +'行著' => '行着', +'行著書' => '行著书', +'行著作' => '行著作', +'行著名' => '行著名', +'行著錄' => '行著录', +'行著稱' => '行著称', +'行著者' => '行著者', +'行著述' => '行著述', +'衣著' => '衣着', +'衣著書' => '衣著书', +'衣著作' => '衣著作', +'衣著名' => '衣著名', +'衣著錄' => '衣著录', +'衣著稱' => '衣著称', +'衣著者' => '衣著者', +'衣著述' => '衣著述', +'裝著' => '装着', +'裝著書' => '装著书', +'裝著作' => '装著作', +'裝著名' => '装著名', +'裝著錄' => '装著录', +'裝著稱' => '装著称', +'裝著者' => '装著者', +'裝著述' => '装著述', +'裹著' => '裹着', +'裹著書' => '裹著书', +'裹著作' => '裹著作', +'裹著名' => '裹著名', +'裹著錄' => '裹著录', +'裹著稱' => '裹著称', +'裹著者' => '裹著者', +'裹著述' => '裹著述', +'覆蓋' => '覆蓋', +'見微知著' => '见微知著', +'見著' => '见着', +'見著書' => '见著书', +'見著作' => '见著作', +'見著名' => '见著名', +'見著錄' => '见著录', +'見著稱' => '见著称', +'見著者' => '见著者', +'見著述' => '见著述', +'視微知著' => '视微知著', +'言幾析理' => '言幾析理', +'記著' => '记着', +'記著書' => '记著书', +'記著作' => '记著作', +'記著名' => '记著名', +'記著錄' => '记著录', +'記著稱' => '记著称', +'記著者' => '记著者', +'記著述' => '记著述', +'論著' => '论著', +'譯著' => '译著', +'試著' => '试着', +'試著書' => '试著书', +'試著作' => '试著作', +'試著名' => '试著名', +'試著錄' => '试著录', +'試著稱' => '试著称', +'試著者' => '试著者', +'試著述' => '试著述', +'語著' => '语着', +'語著書' => '语著书', +'語著作' => '语著作', +'語著名' => '语著名', +'語著錄' => '语著录', +'語著稱' => '语著称', +'語著者' => '语著者', +'語著述' => '语著述', +'豫著' => '豫着', +'豫著書' => '豫著书', +'豫著作' => '豫著作', +'豫著名' => '豫著名', +'豫著錄' => '豫著录', +'豫著稱' => '豫著称', +'豫著者' => '豫著者', +'豫著述' => '豫著述', +'貞著' => '贞着', +'貞著書' => '贞著书', +'貞著作' => '贞著作', +'貞著名' => '贞著名', +'貞著錄' => '贞著录', +'貞著稱' => '贞著称', +'貞著者' => '贞著者', +'貞著述' => '贞著述', +'走著' => '走着', +'走著書' => '走著书', +'走著作' => '走著作', +'走著名' => '走著名', +'走著錄' => '走著录', +'走著稱' => '走著称', +'走著者' => '走著者', +'走著述' => '走著述', +'趕著' => '赶着', +'趕著書' => '赶著书', +'趕著作' => '赶著作', +'趕著名' => '赶著名', +'趕著錄' => '赶著录', +'趕著稱' => '赶著称', +'趕著者' => '赶著者', +'趕著述' => '赶著述', +'趴著' => '趴着', +'趴著書' => '趴著书', +'趴著作' => '趴著作', +'趴著名' => '趴著名', +'趴著錄' => '趴著录', +'趴著稱' => '趴著称', +'趴著者' => '趴著者', +'趴著述' => '趴著述', +'躍著' => '跃着', +'躍著書' => '跃著书', +'躍著作' => '跃著作', +'躍著名' => '跃著名', +'躍著錄' => '跃著录', +'躍著稱' => '跃著称', +'躍著者' => '跃著者', +'躍著述' => '跃著述', +'跑著' => '跑着', +'跑著書' => '跑著书', +'跑著作' => '跑著作', +'跑著名' => '跑著名', +'跑著錄' => '跑著录', +'跑著稱' => '跑著称', +'跑著者' => '跑著者', +'跑著述' => '跑著述', +'跟著' => '跟着', +'跟著書' => '跟著书', +'跟著作' => '跟著作', +'跟著名' => '跟著名', +'跟著錄' => '跟著录', +'跟著稱' => '跟著称', +'跟著者' => '跟著者', +'跟著述' => '跟著述', +'跪著' => '跪着', +'跪著書' => '跪著书', +'跪著作' => '跪著作', +'跪著名' => '跪著名', +'跪著錄' => '跪著录', +'跪著稱' => '跪著称', +'跪著者' => '跪著者', +'跪著述' => '跪著述', +'跳著' => '跳着', +'跳著書' => '跳著书', +'跳著作' => '跳著作', +'跳著名' => '跳著名', +'跳著錄' => '跳著录', +'跳著稱' => '跳著称', +'跳著者' => '跳著者', +'跳著述' => '跳著述', +'躊躇滿志' => '踌躇滿志', +'踏著' => '踏着', +'踏著書' => '踏著书', +'踏著作' => '踏著作', +'踏著名' => '踏著名', +'踏著錄' => '踏著录', +'踏著稱' => '踏著称', +'踏著者' => '踏著者', +'踏著述' => '踏著述', +'踩著' => '踩着', +'踩著書' => '踩著书', +'踩著作' => '踩著作', +'踩著名' => '踩著名', +'踩著錄' => '踩著录', +'踩著稱' => '踩著称', +'踩著者' => '踩著者', +'踩著述' => '踩著述', +'身著' => '身着', +'身著書' => '身著书', +'身著作' => '身著作', +'身著名' => '身著名', +'身著錄' => '身著录', +'身著稱' => '身著称', +'身著者' => '身著者', +'身著述' => '身著述', +'躺著' => '躺着', +'躺著書' => '躺著书', +'躺著作' => '躺著作', +'躺著名' => '躺著名', +'躺著錄' => '躺著录', +'躺著稱' => '躺著称', +'躺著者' => '躺著者', +'躺著述' => '躺著述', +'轉著' => '转着', +'轉著書' => '转著书', +'轉著作' => '转著作', +'轉著名' => '转著名', +'轉著錄' => '转著录', +'轉著稱' => '转著称', +'轉著者' => '转著者', +'轉著述' => '转著述', +'載著' => '载着', +'載著書' => '载著书', +'載著作' => '载著作', +'載著名' => '载著名', +'載著錄' => '载著录', +'載著稱' => '载著称', +'載著者' => '载著者', +'載著述' => '载著述', +'較著' => '较著', +'達著' => '达着', +'達著書' => '达著书', +'達著作' => '达著作', +'達著名' => '达著名', +'達著錄' => '达著录', +'達著稱' => '达著称', +'達著者' => '达著者', +'達著述' => '达著述', +'遠著' => '远着', +'遠著書' => '远著书', +'遠著作' => '远著作', +'遠著名' => '远著名', +'遠著錄' => '远著录', +'遠著稱' => '远著称', +'遠著者' => '远著者', +'遠著述' => '远著述', +'連著' => '连着', +'連著書' => '连著书', +'連著作' => '连著作', +'連著名' => '连著名', +'連著錄' => '连著录', +'連著稱' => '连著称', +'連著者' => '连著者', +'連著述' => '连著述', +'追著' => '追着', +'追著書' => '追著书', +'追著作' => '追著作', +'追著名' => '追著名', +'追著錄' => '追著录', +'追著稱' => '追著称', +'追著者' => '追著者', +'追著述' => '追著述', +'逆著' => '逆着', +'逆著書' => '逆著书', +'逆著作' => '逆著作', +'逆著名' => '逆著名', +'逆著錄' => '逆著录', +'逆著稱' => '逆著称', +'逆著者' => '逆著者', +'逆著述' => '逆著述', +'逼著' => '逼着', +'逼著書' => '逼著书', +'逼著作' => '逼著作', +'逼著名' => '逼著名', +'逼著錄' => '逼著录', +'逼著稱' => '逼著称', +'逼著者' => '逼著者', +'逼著述' => '逼著述', +'遇著' => '遇着', +'遇著書' => '遇著书', +'遇著作' => '遇著作', +'遇著名' => '遇著名', +'遇著錄' => '遇著录', +'遇著稱' => '遇著称', +'遇著者' => '遇著者', +'遇著述' => '遇著述', +'遺著' => '遗著', +'那麽' => '那麽', +'郭子乾' => '郭子乾', +'配著' => '配着', +'配著書' => '配著书', +'配著作' => '配著作', +'配著名' => '配著名', +'配著錄' => '配著录', +'配著稱' => '配著称', +'配著者' => '配著者', +'配著述' => '配著述', +'釀著' => '酿着', +'釀著書' => '酿著书', +'釀著作' => '酿著作', +'釀著名' => '酿著名', +'釀著錄' => '酿著录', +'釀著稱' => '酿著称', +'釀著者' => '酿著者', +'釀著述' => '酿著述', +'重覆' => '重复', +'金鍊' => '金链', +'鐵鍊' => '铁链', +'鉸鍊' => '铰链', +'銀鍊' => '银链', +'鋪著' => '铺着', +'鋪著書' => '铺著书', +'鋪著作' => '铺著作', +'鋪著名' => '铺著名', +'鋪著錄' => '铺著录', +'鋪著稱' => '铺著称', +'鋪著者' => '铺著者', +'鋪著述' => '铺著述', +'鍊子' => '链子', +'鍊條' => '链条', +'鍊鎖' => '链锁', +'鍊錘' => '链锤', +'鎖鍊' => '锁链', +'鍾鍛' => '锺锻', +'鍛鍾' => '锻锺', +'閉著' => '闭着', +'閉著書' => '闭著书', +'閉著作' => '闭著作', +'閉著名' => '闭著名', +'閉著錄' => '闭著录', +'閉著稱' => '闭著称', +'閉著者' => '闭著者', +'閉著述' => '闭著述', +'閑著' => '闲着', +'閑著書' => '闲著书', +'閑著作' => '闲著作', +'閑著名' => '闲著名', +'閑著錄' => '闲著录', +'閑著稱' => '闲著称', +'閑著者' => '闲著者', +'閑著述' => '闲著述', +'附著' => '附着', +'附睪' => '附睾', +'附著書' => '附著书', +'附著作' => '附著作', +'附著名' => '附著名', +'附著錄' => '附著录', +'附著稱' => '附著称', +'附著者' => '附著者', +'附著述' => '附著述', +'陋著' => '陋着', +'陋著書' => '陋著书', +'陋著作' => '陋著作', +'陋著名' => '陋著名', +'陋著錄' => '陋著录', +'陋著稱' => '陋著称', +'陋著者' => '陋著者', +'陋著述' => '陋著述', +'陪著' => '陪着', +'陪著書' => '陪著书', +'陪著作' => '陪著作', +'陪著名' => '陪著名', +'陪著錄' => '陪著录', +'陪著稱' => '陪著称', +'陪著者' => '陪著者', +'陪著述' => '陪著述', +'陳堵' => '陳堵', +'陳禕' => '陳禕', +'隨著' => '随着', +'隨著書' => '随著书', +'隨著作' => '随著作', +'隨著名' => '随著名', +'隨著錄' => '随著录', +'隨著稱' => '随著称', +'隨著者' => '随著者', +'隨著述' => '随著述', +'隔著' => '隔着', +'隔著書' => '隔著书', +'隔著作' => '隔著作', +'隔著名' => '隔著名', +'隔著錄' => '隔著录', +'隔著稱' => '隔著称', +'隔著者' => '隔著者', +'隔著述' => '隔著述', +'隱睪' => '隱睾', +'雅著' => '雅着', +'雅著書' => '雅著书', +'雅著作' => '雅著作', +'雅著名' => '雅著名', +'雅著錄' => '雅著录', +'雅著稱' => '雅著称', +'雅著者' => '雅著者', +'雅著述' => '雅著述', +'雍乾' => '雍乾', +'頂著' => '顶着', +'頂著書' => '顶著书', +'頂著作' => '顶著作', +'頂著名' => '顶著名', +'頂著錄' => '顶著录', +'頂著稱' => '顶著称', +'頂著者' => '顶著者', +'頂著述' => '顶著述', +'項鍊' => '项链', +'順著' => '顺着', +'順著書' => '顺著书', +'順著作' => '顺著作', +'順著名' => '顺著名', +'順著錄' => '顺著录', +'順著稱' => '顺著称', +'順著者' => '顺著者', +'順著述' => '顺著述', +'領著' => '领着', +'領著書' => '领著书', +'領著作' => '领著作', +'領著名' => '领著名', +'領著錄' => '领著录', +'領著稱' => '领著称', +'領著者' => '领著者', +'領著述' => '领著述', +'飄著' => '飘着', +'飄著書' => '飘著书', +'飄著作' => '飘著作', +'飄著名' => '飘著名', +'飄著錄' => '飘著录', +'飄著稱' => '飘著称', +'飄著者' => '飘著者', +'飄著述' => '飘著述', +'飭令' => '飭令', +'餘年' => '馀年', +'駕著' => '驾着', +'駕著書' => '驾著书', +'駕著作' => '驾著作', +'駕著名' => '驾著名', +'駕著錄' => '驾著录', +'駕著稱' => '驾著称', +'駕著者' => '驾著者', +'駕著述' => '驾著述', +'罵著' => '骂着', +'罵著書' => '骂著书', +'罵著作' => '骂著作', +'罵著名' => '骂著名', +'罵著錄' => '骂著录', +'罵著稱' => '骂著称', +'罵著者' => '骂著者', +'罵著述' => '骂著述', +'騎著' => '骑着', +'騎著書' => '骑著书', +'騎著作' => '骑著作', +'騎著名' => '骑著名', +'騎著錄' => '骑著录', +'騎著稱' => '骑著称', +'騎著者' => '骑著者', +'騎著述' => '骑著述', +'騙著' => '骗着', +'騙著書' => '骗著书', +'騙著作' => '骗著作', +'騙著名' => '骗著名', +'騙著錄' => '骗著录', +'騙著稱' => '骗著称', +'騙著者' => '骗著者', +'騙著述' => '骗著述', +'高著' => '高着', +'高著書' => '高著书', +'高著作' => '高著作', +'高著名' => '高著名', +'高著錄' => '高著录', +'高著稱' => '高著称', +'高著者' => '高著者', +'高著述' => '高著述', +'髭著' => '髭着', +'髭著書' => '髭著书', +'髭著作' => '髭著作', +'髭著名' => '髭著名', +'髭著錄' => '髭著录', +'髭著稱' => '髭著称', +'髭著者' => '髭著者', +'髭著述' => '髭著述', +'鬱姓' => '鬱姓', +'鬱氏' => '鬱氏', +'魏徵' => '魏徵', +'鯰魚' => '鲶鱼', +'麯崇裕' => '麯崇裕', +'麴義' => '麴义', +'麴义' => '麴义', +'麴英' => '麴英', +'麽氏' => '麽氏', +'麽麽' => '麽麽', +'麼麼' => '麽麽', +'黏著' => '黏着', +'黏著書' => '黏著书', +'黏著作' => '黏著作', +'黏著名' => '黏著名', +'黏著錄' => '黏著录', +'黏著稱' => '黏著称', +'黏著者' => '黏著者', +'黏著述' => '黏著述', ); $zh2TW = array( -"”" => "」", -"“" => "「", -"‘" => "『", -"’" => "』", -"着" => "著", -"元凶" => "元凶", -"凶器" => "凶器", -"凶徒" => "凶徒", -"凶手" => "凶手", -"凶案" => "凶案", -"凶残" => "凶殘", -"凶杀" => "凶殺", -"疑凶" => "疑凶", -"真凶" => "真凶", -"缉凶" => "緝凶", -"行凶" => "行凶", -"行凶后" => "行凶後", -"买凶" => "買凶", -"追凶" => "追凶", -"复苏" => "復甦", -"復蘇" => "復甦", -"缺省" => "預設", -"串行" => "串列", -"串列加速器" => "串列加速器", -"以太网" => "乙太網", -"位图" => "點陣圖", -"例程" => "常式", -"信道" => "通道", -"光标" => "游標", -"光盘" => "光碟", -"光驱" => "光碟機", -"全角" => "全形", -"加载" => "載入", -"半角" => "半形", -"变量" => "變數", -"噪声" => "雜訊", -"脱机" => "離線", -"声卡" => "音效卡", -"老字号" => "老字號", -"连字号" => "連字號", -"字号" => "字型大小", -"字库" => "字型檔", -"字段" => "欄位", -"字符" => "字元", -"字符集" => "字符集", -"存盘" => "存檔", -"寻址" => "定址", -"尾注" => "章節附註", -"异步" => "非同步", -"总线" => "匯流排", -"括号" => "括弧", -"接口" => "介面", -"控件" => "控制項", -"权限" => "許可權", -"盘片" => "碟片", -"硅片" => "矽片", -"硅谷" => "矽谷", -"硬盘" => "硬碟", -"磁盘" => "磁碟", -"磁道" => "磁軌", -"程控" => "程式控制", -"远程控制" => "遠程控制", -"遠程控制" => "遠程控制", -"行程控制" => "行程控制", -"端口" => "埠", -"算子" => "運算元", -"算法" => "演算法", -"芯片" => "晶片", -"芯片" => "晶元", -"词组" => "片語", -"译码" => "解碼", -"软驱" => "軟碟機", -"快闪存储器" => "快閃記憶體", -"闪存" => "快閃記憶體", -"鼠标" => "滑鼠", -"进制" => "進位", -"交互式" => "互動式", -"仿真" => "模擬", -"优先级" => "優先順序", -"传感" => "感測", -"便携式" => "攜帶型", -"信息论" => "資訊理論", -"写保护" => "防寫", -"分布式" => "分散式", -"分辨率" => "解析度", -"服务器" => "伺服器", -"等于" => "等於", -"局域网" => "區域網", -"扫瞄仪" => "掃瞄器", -"宽带" => "寬頻", -"数据库" => "資料庫", -"奶酪" => "乳酪", -"巨商" => "鉅賈", -"手电" => "手電筒", -"手电筒" => "手電筒", -"万历" => "萬曆", -"永历" => "永曆", -"词汇" => "辭彙", -"习用" => "慣用", -"元音" => "母音", -"宋元" => "宋元", -"任意球" => "自由球", -"头球" => "頭槌", -"入球" => "進球", -"粒入球" => "顆進球", -"打门" => "射門", -"火锅盖帽" => "蓋火鍋", -"打印机" => "印表機", -"打印機" => "印表機", -"字节" => "位元組", -"字節" => "位元組", -"打印" => "列印", -"打印" => "列印", -"硬件" => "硬體", -"硬件" => "硬體", -"二极管" => "二極體", -"二極管" => "二極體", -"三极管" => "三極體", -"三極管" => "三極體", -"软件" => "軟體", -"軟件" => "軟體", -"网络" => "網路", -"網絡" => "網路", -"人工智能" => "人工智慧", -"航天飞机" => "太空梭", -"航天大学" => "航天大學", -"穿梭機" => "太空梭", -"因特网" => "網際網路", -"互聯網" => "網際網路", -"机器人" => "機器人", -"機械人" => "機器人", -"移动电话" => "行動電話", -"流動電話" => "行動電話", -"调制解调器" => "數據機", -"調制解調器" => "數據機", -"短信" => "簡訊", -"短訊" => "簡訊", -"乌兹别克斯坦" => "烏茲別克", -"乍得" => "查德", -"乍得" => "查德", -"也门" => "葉門", -"也門" => "葉門", -"伯利兹" => "貝里斯", -"伯利茲" => "貝里斯", -"佛得角" => "維德角", -"克罗地亚" => "克羅埃西亞", -"克羅地亞" => "克羅埃西亞", -"冈比亚" => "甘比亞", -"岡比亞" => "甘比亞", -"几内亚比绍" => "幾內亞比索", -"幾內亞比紹" => "幾內亞比索", -"列支敦士登" => "列支敦斯登", -"列支敦士登" => "列支敦斯登", -"利比里亚" => "賴比瑞亞", -"利比里亞" => "賴比瑞亞", -"加纳" => "迦納", -"加納" => "迦納", -"加蓬" => "加彭", -"加蓬" => "加彭", -"博茨瓦纳" => "波札那", -"博茨瓦納" => "波札那", -"卡塔尔" => "卡達", -"卡塔爾" => "卡達", -"卢旺达" => "盧安達", -"盧旺達" => "盧安達", -"危地马拉" => "瓜地馬拉", -"危地馬拉" => "瓜地馬拉", -"厄瓜多尔" => "厄瓜多", -"厄瓜多爾" => "厄瓜多", -"厄立特里亚" => "厄利垂亞", -"厄立特里亞" => "厄利垂亞", -"吉布提" => "吉布地", -"吉布堤" => "吉布地", -"哈萨克斯坦" => "哈薩克", -"哥斯达黎加" => "哥斯大黎加", -"哥斯達黎加" => "哥斯大黎加", -"图瓦卢" => "吐瓦魯", -"圖瓦盧" => "吐瓦魯", -"土库曼斯坦" => "土庫曼", -"圣卢西亚" => "聖露西亞", -"聖盧西亞" => "聖露西亞", -"圣基茨和尼维斯" => "聖克里斯多福及尼維斯", -"聖吉斯納域斯" => "聖克里斯多福及尼維斯", -"圣文森特和格林纳丁斯" => "聖文森及格瑞那丁", -"聖文森特和格林納丁斯" => "聖文森及格瑞那丁", -"圣马力诺" => "聖馬利諾", -"聖馬力諾" => "聖馬利諾", -"圭亚那" => "蓋亞那", -"圭亞那" => "蓋亞那", -"坦桑尼亚" => "坦尚尼亞", -"坦桑尼亞" => "坦尚尼亞", -"埃塞俄比亚" => "衣索比亞", -"埃塞俄比亞" => "衣索比亞", -"基里巴斯" => "吉里巴斯", -"基里巴斯" => "吉里巴斯", -"塔吉克斯坦" => "塔吉克", -"塞拉利昂" => "獅子山", -"塞拉利昂" => "獅子山", -"塞浦路斯" => "塞普勒斯", -"塞浦路斯" => "塞普勒斯", -"塞舌尔" => "塞席爾", -"塞舌爾" => "塞席爾", -"多米尼加共和国" => "多明尼加", -"多米尼加共和國" => "多明尼加", -"多明尼加共和國" => "多明尼加", -"多米尼加国" => "多米尼克", -"多明尼加國" => "多米尼克", -"安提瓜和巴布达" => "安地卡及巴布達", -"安提瓜和巴布達" => "安地卡及巴布達", -"尼日利亚" => "奈及利亞", -"尼日利亞" => "奈及利亞", -"尼日尔" => "尼日", -"尼日爾" => "尼日", -"巴巴多斯" => "巴貝多", -"巴布亚新几内亚" => "巴布亞紐幾內亞", -"巴布亞新畿內亞" => "巴布亞紐幾內亞", -"布基纳法索" => "布吉納法索", -"布基納法索" => "布吉納法索", -"布隆迪" => "蒲隆地", -"布隆迪" => "蒲隆地", -"帕劳" => "帛琉", -"意大利" => "義大利", -"所罗门群岛" => "索羅門群島", -"所羅門群島" => "索羅門群島", -"文莱" => "汶萊", -"斯威士兰" => "史瓦濟蘭", -"斯威士蘭" => "史瓦濟蘭", -"斯洛文尼亚" => "斯洛維尼亞", -"斯洛文尼亞" => "斯洛維尼亞", -"新西兰" => "紐西蘭", -"新西蘭" => "紐西蘭", -"格林纳达" => "格瑞那達", -"格林納達" => "格瑞那達", -"格鲁吉亚" => "喬治亞", -"格魯吉亞" => "喬治亞", -"佐治亚" => "喬治亞", -"佐治亞" => "喬治亞", -"毛里塔尼亚" => "茅利塔尼亞", -"毛里塔尼亞" => "茅利塔尼亞", -"毛里求斯" => "模里西斯", -"毛里裘斯" => "模里西斯", -"沙特阿拉伯" => "沙烏地阿拉伯", -"沙地阿拉伯" => "沙烏地阿拉伯", -"波斯尼亚和黑塞哥维那" => "波士尼亞赫塞哥維納", -"波斯尼亞黑塞哥維那" => "波士尼亞赫塞哥維納", -"津巴布韦" => "辛巴威", -"津巴布韋" => "辛巴威", -"洪都拉斯" => "宏都拉斯", -"洪都拉斯" => "宏都拉斯", -"特立尼达和托巴哥" => "千里達托貝哥", -"特立尼達和多巴哥" => "千里達托貝哥", -"瑙鲁" => "諾魯", -"瑙魯" => "諾魯", -"瓦努阿图" => "萬那杜", -"瓦努阿圖" => "萬那杜", -"溫納圖萬" => "那杜", -"科摩罗" => "葛摩", -"科摩羅" => "葛摩", -"科特迪瓦" => "象牙海岸", -"突尼斯" => "突尼西亞", -"索马里" => "索馬利亞", -"索馬里" => "索馬利亞", -"老挝" => "寮國", -"老撾" => "寮國", -"肯尼亚" => "肯亞", -"肯雅" => "肯亞", -"苏里南" => "蘇利南", -"莫桑比克" => "莫三比克", -"莱索托" => "賴索托", -"萊索托" => "賴索托", -"贝宁" => "貝南", -"貝寧" => "貝南", -"赞比亚" => "尚比亞", -"贊比亞" => "尚比亞", -"阿塞拜疆" => "亞塞拜然", -"阿拉伯联合酋长国" => "阿拉伯聯合大公國", -"阿拉伯聯合酋長國" => "阿拉伯聯合大公國", -"马尔代夫" => "馬爾地夫", -"馬爾代夫" => "馬爾地夫", -"马耳他" => "馬爾他", -"马里共和国" => "馬利共和國", -"馬里共和國" => "馬利共和國", -"方便面" => "速食麵", -"快速面" => "速食麵", -"即食麵" => "速食麵", -"薯仔" => "土豆", -"土豆网" => "土豆網", -"土豆網" => "土豆網", -"蹦极跳" => "笨豬跳", -"绑紧跳" => "笨豬跳", -"冷菜" => "冷盤", -"凉菜" => "冷盤", -"出租车" => "計程車", -"台球" => "撞球", -"桌球" => "撞球", -"雪糕" => "冰淇淋", -"卫生" => "衛生", -"衞生" => "衛生", -"平治之亂" => "平治之亂", -"平治之乱" => "平治之亂", -"平治" => "賓士", -"奔驰" => "賓士", -"積架" => "捷豹", -"福士" => "福斯", -"雪铁龙" => "雪鐵龍", -"萬事得" => "馬自達", -"拿破仑" => "拿破崙", -"拿破侖" => "拿破崙", -"布什" => "布希", -"布殊" => "布希", -"克林顿" => "柯林頓", -"克林頓" => "柯林頓", -"侯赛因" => "海珊", -"侯賽因" => "海珊", -"凡高" => "梵谷", -"狄安娜" => "黛安娜", -"戴安娜" => "黛安娜", +'“' => '「', +'”' => '」', +'‘' => '『', +'’' => '』', +'三極管' => '三極體', +'三极管' => '三極體', +'串行' => '串列', +'串列加速器' => '串列加速器', +'以太网' => '乙太網', +'奶酪' => '乳酪', +'二極管' => '二極體', +'二极管' => '二極體', +'交互式' => '互動式', +'阿塞拜疆' => '亞塞拜然', +'人工智能' => '人工智慧', +'接口' => '介面', +'服务器' => '伺服器', +'字節' => '位元組', +'字节' => '位元組', +'优先级' => '優先順序', +'元兇' => '元凶', +'元凶' => '元凶', +'光盘' => '光碟', +'光驱' => '光碟機', +'克羅地亞' => '克羅埃西亞', +'克罗地亚' => '克羅埃西亞', +'全角' => '全形', +'雪糕' => '冰淇淋', +'凉菜' => '冷盤', +'冷菜' => '冷盤', +'凶器' => '凶器', +'兇器' => '凶器', +'凶徒' => '凶徒', +'兇徒' => '凶徒', +'兇手' => '凶手', +'凶手' => '凶手', +'兇案' => '凶案', +'凶案' => '凶案', +'凶殘' => '凶殘', +'兇殘' => '凶殘', +'凶残' => '凶殘', +'兇殺' => '凶殺', +'凶杀' => '凶殺', +'凶殺' => '凶殺', +'分布式' => '分散式', +'打印' => '列印', +'列支敦士登' => '列支敦斯登', +'剪彩' => '剪綵', +'加蓬' => '加彭', +'总线' => '匯流排', +'局域网' => '區域網', +'特立尼達和多巴哥' => '千里達托貝哥', +'特立尼达和托巴哥' => '千里達托貝哥', +'半角' => '半形', +'卡塔爾' => '卡達', +'卡塔尔' => '卡達', +'打印機' => '印表機', +'打印机' => '印表機', +'厄立特里亞' => '厄利垂亞', +'厄立特里亚' => '厄利垂亞', +'厄瓜多尔' => '厄瓜多', +'厄瓜多爾' => '厄瓜多', +'斯威士兰' => '史瓦濟蘭', +'斯威士蘭' => '史瓦濟蘭', +'吉布提' => '吉布地', +'吉布堤' => '吉布地', +'基里巴斯' => '吉里巴斯', +'圖瓦盧' => '吐瓦魯', +'图瓦卢' => '吐瓦魯', +'哈萨克斯坦' => '哈薩克', +'哥斯達黎加' => '哥斯大黎加', +'哥斯达黎加' => '哥斯大黎加', +'格魯吉亞' => '喬治亞', +'格鲁吉亚' => '喬治亞', +'佐治亚' => '喬治亞', +'佐治亞' => '喬治亞', +'土库曼斯坦' => '土庫曼', +'薯仔' => '土豆', +'土豆網' => '土豆網', +'土豆网' => '土豆網', +'坦桑尼亚' => '坦尚尼亞', +'坦桑尼亞' => '坦尚尼亞', +'端口' => '埠', +'塔吉克斯坦' => '塔吉克', +'塞舌尔' => '塞席爾', +'塞舌爾' => '塞席爾', +'塞浦路斯' => '塞普勒斯', +'多明尼加共和國' => '多明尼加', +'多米尼加共和国' => '多明尼加', +'多米尼加共和國' => '多明尼加', +'多米尼加国' => '多米尼克', +'多明尼加國' => '多米尼克', +'穿梭機' => '太空梭', +'航天飞机' => '太空梭', +'尼日利亚' => '奈及利亞', +'尼日利亞' => '奈及利亞', +'字符' => '字元', +'字号' => '字型大小', +'字库' => '字型檔', +'字符集' => '字符集', +'存盘' => '存檔', +'安提瓜和巴布達' => '安地卡及巴布達', +'安提瓜和巴布达' => '安地卡及巴布達', +'宋元' => '宋元', +'洪都拉斯' => '宏都拉斯', +'寻址' => '定址', +'宽带' => '寬頻', +'老撾' => '寮國', +'老挝' => '寮國', +'打门' => '射門', +'贊比亞' => '尚比亞', +'赞比亚' => '尚比亞', +'尼日爾' => '尼日', +'尼日尔' => '尼日', +'巴布亞新畿內亞' => '巴布亞紐幾內亞', +'巴布亚新几内亚' => '巴布亞紐幾內亞', +'巴巴多斯' => '巴貝多', +'布基纳法索' => '布吉納法索', +'布基納法索' => '布吉納法索', +'布什' => '布希', +'布殊' => '布希', +'帕劳' => '帛琉', +'例程' => '常式', +'平治之乱' => '平治之亂', +'平治之亂' => '平治之亂', +'几内亚比绍' => '幾內亞比索', +'幾內亞比紹' => '幾內亞比索', +'彩带' => '彩帶', +'彩排' => '彩排', +'彩楼' => '彩樓', +'彩牌楼' => '彩牌樓', +'復蘇' => '復甦', +'复苏' => '復甦', +'快闪存储器' => '快閃記憶體', +'闪存' => '快閃記憶體', +'传感' => '感測', +'习用' => '慣用', +'戏彩娱亲' => '戲綵娛親', +'手电筒' => '手電筒', +'手电' => '手電筒', +'括号' => '括弧', +'拿破侖' => '拿破崙', +'拿破仑' => '拿破崙', +'積架' => '捷豹', +'扫瞄仪' => '掃瞄器', +'控件' => '控制項', +'台球' => '撞球', +'桌球' => '撞球', +'便携式' => '攜帶型', +'调制解调器' => '數據機', +'調制解調器' => '數據機', +'斯洛文尼亞' => '斯洛維尼亞', +'斯洛文尼亚' => '斯洛維尼亞', +'新纪元' => '新紀元', +'新紀元' => '新紀元', +'芯片' => '晶元', +'乍得' => '查德', +'克林頓' => '柯林頓', +'克林顿' => '柯林頓', +'格林納達' => '格瑞那達', +'格林纳达' => '格瑞那達', +'凡高' => '梵谷', +'榴蓮' => '榴槤', +'榴莲' => '榴槤', +'仿真' => '模擬', +'毛里裘斯' => '模里西斯', +'毛里求斯' => '模里西斯', +'機械人' => '機器人', +'机器人' => '機器人', +'字段' => '欄位', +'元音' => '母音', +'永历' => '永曆', +'文莱' => '汶萊', +'沙特阿拉伯' => '沙烏地阿拉伯', +'沙地阿拉伯' => '沙烏地阿拉伯', +'波斯尼亞黑塞哥維那' => '波士尼亞赫塞哥維納', +'波斯尼亚和黑塞哥维那' => '波士尼亞赫塞哥維納', +'博茨瓦纳' => '波札那', +'博茨瓦納' => '波札那', +'侯赛因' => '海珊', +'侯賽因' => '海珊', +'光标' => '游標', +'鼠标' => '滑鼠', +'算法' => '演算法', +'乌兹别克斯坦' => '烏茲別克', +'词组' => '片語', +'塞拉利昂' => '獅子山', +'危地马拉' => '瓜地馬拉', +'危地馬拉' => '瓜地馬拉', +'冈比亚' => '甘比亞', +'岡比亞' => '甘比亞', +'疑兇' => '疑凶', +'疑凶' => '疑凶', +'盧旺達' => '盧安達', +'卢旺达' => '盧安達', +'真凶' => '真凶', +'真兇' => '真凶', +'硅片' => '矽片', +'硅谷' => '矽谷', +'硬盘' => '硬碟', +'硬件' => '硬體', +'盘片' => '碟片', +'磁盘' => '磁碟', +'磁道' => '磁軌', +'福士' => '福斯', +'程控' => '程式控制', +'突尼斯' => '突尼西亞', +'尾注' => '章節附註', +'蹦极跳' => '笨豬跳', +'绑紧跳' => '笨豬跳', +'等于' => '等於', +'短訊' => '簡訊', +'短信' => '簡訊', +'新西蘭' => '紐西蘭', +'新西兰' => '紐西蘭', +'所罗门群岛' => '索羅門群島', +'所羅門群島' => '索羅門群島', +'索馬里' => '索馬利亞', +'索马里' => '索馬利亞', +'结彩' => '結綵', +'佛得角' => '維德角', +'網絡' => '網路', +'网络' => '網路', +'互聯網' => '網際網路', +'因特网' => '網際網路', +'彩球' => '綵球', +'彩绸' => '綵綢', +'彩线' => '綵線', +'彩船' => '綵船', +'彩衣' => '綵衣', +'缉凶' => '緝凶', +'緝兇' => '緝凶', +'緝凶' => '緝凶', +'意大利' => '義大利', +'老字号' => '老字號', +'圣基茨和尼维斯' => '聖克里斯多福及尼維斯', +'聖吉斯納域斯' => '聖克里斯多福及尼維斯', +'聖文森特和格林納丁斯' => '聖文森及格瑞那丁', +'圣文森特和格林纳丁斯' => '聖文森及格瑞那丁', +'圣卢西亚' => '聖露西亞', +'聖盧西亞' => '聖露西亞', +'圣马力诺' => '聖馬利諾', +'聖馬力諾' => '聖馬利諾', +'肯尼亚' => '肯亞', +'肯雅' => '肯亞', +'任意球' => '自由球', +'航天大学' => '航天大學', +'毛里塔尼亚' => '茅利塔尼亞', +'毛里塔尼亞' => '茅利塔尼亞', +'莫桑比克' => '莫三比克', +'万历' => '萬曆', +'瓦努阿图' => '萬那杜', +'瓦努阿圖' => '萬那杜', +'也门' => '葉門', +'也門' => '葉門', +'着' => '著', +'科摩羅' => '葛摩', +'科摩罗' => '葛摩', +'布隆迪' => '蒲隆地', +'圭亞那' => '蓋亞那', +'圭亚那' => '蓋亞那', +'火锅盖帽' => '蓋火鍋', +'苏里南' => '蘇利南', +'行凶' => '行凶', +'行兇' => '行凶', +'行凶后' => '行凶後', +'行兇後' => '行凶後', +'行凶後' => '行凶後', +'流動電話' => '行動電話', +'移动电话' => '行動電話', +'行程控制' => '行程控制', +'卫生' => '衛生', +'衞生' => '衛生', +'埃塞俄比亚' => '衣索比亞', +'埃塞俄比亞' => '衣索比亞', +'分辨率' => '解析度', +'译码' => '解碼', +'出租车' => '計程車', +'权限' => '許可權', +'瑙鲁' => '諾魯', +'瑙魯' => '諾魯', +'变量' => '變數', +'科特迪瓦' => '象牙海岸', +'貝寧' => '貝南', +'贝宁' => '貝南', +'伯利茲' => '貝里斯', +'伯利兹' => '貝里斯', +'買兇' => '買凶', +'买凶' => '買凶', +'買凶' => '買凶', +'数据库' => '資料庫', +'信息论' => '資訊理論', +'奔驰' => '賓士', +'平治' => '賓士', +'利比里亚' => '賴比瑞亞', +'利比里亞' => '賴比瑞亞', +'萊索托' => '賴索托', +'莱索托' => '賴索托', +'软驱' => '軟碟機', +'軟件' => '軟體', +'软件' => '軟體', +'加载' => '載入', +'津巴布韦' => '辛巴威', +'津巴布韋' => '辛巴威', +'词汇' => '辭彙', +'加纳' => '迦納', +'加納' => '迦納', +'追凶' => '追凶', +'追兇' => '追凶', +'信道' => '通道', +'逞凶鬥狠' => '逞凶鬥狠', +'逞兇鬥狠' => '逞凶鬥狠', +'逞凶斗狠' => '逞凶鬥狠', +'即食麵' => '速食麵', +'方便面' => '速食麵', +'快速面' => '速食麵', +'连字号' => '連字號', +'进制' => '進位', +'入球' => '進球', +'算子' => '運算元', +'遠程控制' => '遠程控制', +'远程控制' => '遠程控制', +'溫納圖萬' => '那杜', +'巨商' => '鉅賈', +'写保护' => '防寫', +'阿拉伯联合酋长国' => '阿拉伯聯合大公國', +'阿拉伯聯合酋長國' => '阿拉伯聯合大公國', +'噪声' => '雜訊', +'脱机' => '離線', +'雪铁龙' => '雪鐵龍', +'异步' => '非同步', +'声卡' => '音效卡', +'缺省' => '預設', +'颁布' => '頒布', +'頒佈' => '頒布', +'头球' => '頭槌', +'粒入球' => '顆進球', +'马里共和国' => '馬利共和國', +'馬里共和國' => '馬利共和國', +'马耳他' => '馬爾他', +'马尔代夫' => '馬爾地夫', +'馬爾代夫' => '馬爾地夫', +'萬事得' => '馬自達', +'狄安娜' => '黛安娜', +'戴安娜' => '黛安娜', +'位图' => '點陣圖', ); $zh2HK = array( -"”" => "」", -"“" => "「", -"‘" => "『", -"’" => "』", -"凶殺" => "兇殺", -"凶殘" => "兇殘", -"緝凶" => "緝兇", -"買凶" => "買兇", -"印表機" => "打印機", -"字节" => "位元組", -"字節" => "位元組", -"列印" => "打印", -"硬件" => "硬件", -"硬體" => "硬件", -"二極體" => "二極管", -"三極體" => "三極管", -"數位" => "數碼", -"軟體" => "軟件", -"網路" => "網絡", -"人工智慧" => "人工智能", -"航天飞机" => "穿梭機", -"太空梭" => "穿梭機", -"因特网" => "互聯網", -"網際網路" => "互聯網", -"机器人" => "機械人", -"機器人" => "機械人", -"移动电话" => "流動電話", -"行動電話" => "流動電話", -"數據機" => "調制解調器", -"短信" => "短訊", -"簡訊" => "短訊", -"查德" => "乍得", -"葉門" => "也門", -"貝里斯" => "伯利茲", -"維德角" => "佛得角", -"克羅埃西亞" => "克羅地亞", -"甘比亞" => "岡比亞", -"幾內亞比索" => "幾內亞比紹", -"列支敦斯登" => "列支敦士登", -"賴比瑞亞" => "利比里亞", -"迦納" => "加納", -"加彭" => "加蓬", -"波札那" => "博茨瓦納", -"卡達" => "卡塔爾", -"盧安達" => "盧旺達", -"瓜地馬拉" => "危地馬拉", -"厄瓜多尔" => "厄瓜多爾", -"厄瓜多爾" => "厄瓜多爾", -"厄瓜多" => "厄瓜多爾", -"厄利垂亞" => "厄立特里亞", -"吉布地" => "吉布堤", -"哥斯大黎加" => "哥斯達黎加", -"吐瓦魯" => "圖瓦盧", -"聖露西亞" => "聖盧西亞", -"圣基茨和尼维斯" => "聖吉斯納域斯", -"聖克里斯多福及尼維斯" => "聖吉斯納域斯", -"聖文森及格瑞那丁" => "聖文森特和格林納丁斯", -"聖馬利諾" => "聖馬力諾", -"蓋亞那" => "圭亞那", -"坦尚尼亞" => "坦桑尼亞", -"衣索匹亞" => "埃塞俄比亞", -"衣索比亞" => "埃塞俄比亞", -"吉里巴斯" => "基里巴斯", -"塞普勒斯" => "塞浦路斯", -"塞席爾" => "塞舌爾", -"多米尼克" => "多明尼加國", -"安地卡及巴布達" => "安提瓜和巴布達", -"尼日利亚" => "尼日利亞", -"尼日利亞" => "尼日利亞", -"奈及利亞" => "尼日利亞", -"尼日尔" => "尼日爾", -"尼日爾" => "尼日爾", -"尼日" => "尼日爾", -"巴貝多" => "巴巴多斯", -"巴布亞紐幾內亞" => "巴布亞新畿內亞", -"布吉納法索" => "布基納法索", -"蒲隆地" => "布隆迪", -"帕劳" => "帛琉", -"義大利" => "意大利", -"索羅門群島" => "所羅門群島", -"文莱" => "汶萊", -"史瓦濟蘭" => "斯威士蘭", -"斯洛維尼亞" => "斯洛文尼亞", -"紐西蘭" => "新西蘭", -"格瑞那達" => "格林納達", -"茅利塔尼亞" => "毛里塔尼亞", -"毛里求斯" => "毛里裘斯", -"模里西斯" => "毛里裘斯", -"沙地阿拉伯" => "沙特阿拉伯", -"沙烏地阿拉伯" => "沙特阿拉伯", -"波士尼亞赫塞哥維納" => "波斯尼亞黑塞哥維那", -"辛巴威" => "津巴布韋", -"宏都拉斯" => "洪都拉斯", -"千里達托貝哥" => "特立尼達和多巴哥", -"諾魯" => "瑙魯", -"萬那杜" => "瓦努阿圖", -"葛摩" => "科摩羅", -"索馬利亞" => "索馬里", -"寮國" => "老撾", -"肯尼亚" => "肯雅", -"肯亞" => "肯雅", -"莫三比克" => "莫桑比克", -"賴索托" => "萊索托", -"貝南" => "貝寧", -"尚比亞" => "贊比亞", -"亞塞拜然" => "阿塞拜疆", -"阿拉伯聯合大公國" => "阿拉伯聯合酋長國", -"馬爾地夫" => "馬爾代夫", -"馬利共和國" => "馬里共和國", -"方便面" => "即食麵", -"快速面" => "即食麵", -"速食麵" => "即食麵", -"泡麵" => "即食麵", -"土豆" => "馬鈴薯", -"土豆网" => "土豆網", -"土豆網" => "土豆網", -"华乐" => "中樂", -"民乐" => "中樂", -"計程車" => "的士", -"出租车" => "的士", -"公車" => "巴士", -"自行车" => "單車", -"犬只" => "狗隻", -"台球" => "桌球", -"撞球" => "桌球", -"冰淇淋" => "雪糕", -"賓士" => "平治", -"捷豹" => "積架", -"福斯" => "福士", -"雪铁龙" => "先進", -"雪鐵龍" => "先進", -"沃尓沃" => "富豪", -"马自达" => "萬事得", -"馬自達" => "萬事得", -"寶獅" => "標致", -"拿破崙" => "拿破侖", -"布什" => "布殊", -"布希" => "布殊", -"柯林頓" => "克林頓", -"萨达姆" => "薩達姆", -"海珊" => "侯賽因", -"大卫·贝克汉姆" => "大衛碧咸", -"迈克尔·欧文" => "米高奧雲", -"珍妮弗·卡普里亚蒂" => "卡佩雅蒂", -"马拉特·萨芬" => "沙芬", -"迈克尔·舒马赫" => "舒麥加", -"希特勒" => "希特拉", -"狄安娜" => "戴安娜", -"黛安娜" => "戴安娜", +'“' => '「', +'”' => '」', +'‘' => '『', +'’' => '』', +'三極體' => '三極管', +'不著痕跡' => '不着痕跡', +'不著邊際' => '不着邊際', +'民乐' => '中樂', +'华乐' => '中樂', +'查德' => '乍得', +'乘著' => '乘着', +'乘著作' => '乘著作', +'乘著名' => '乘著名', +'乘著書' => '乘著書', +'乘著稱' => '乘著稱', +'乘著者' => '乘著者', +'乘著述' => '乘著述', +'乘著錄' => '乘著錄', +'葉門' => '也門', +'二極體' => '二極管', +'網際網路' => '互聯網', +'因特网' => '互聯網', +'亮著' => '亮着', +'亮著作' => '亮著作', +'亮著名' => '亮著名', +'亮著書' => '亮著書', +'亮著稱' => '亮著稱', +'亮著者' => '亮著者', +'亮著述' => '亮著述', +'亮著錄' => '亮著錄', +'人工智慧' => '人工智能', +'仗著' => '仗着', +'仗著作' => '仗著作', +'仗著名' => '仗著名', +'仗著書' => '仗著書', +'仗著稱' => '仗著稱', +'仗著者' => '仗著者', +'仗著述' => '仗著述', +'仗著錄' => '仗著錄', +'代表著' => '代表着', +'代表著作' => '代表著作', +'代表著名' => '代表著名', +'代表著書' => '代表著書', +'代表著稱' => '代表著稱', +'代表著者' => '代表著者', +'代表著述' => '代表著述', +'代表著錄' => '代表著錄', +'貝里斯' => '伯利茲', +'伴著' => '伴着', +'伴著作' => '伴著作', +'伴著名' => '伴著名', +'伴著書' => '伴著書', +'伴著稱' => '伴著稱', +'伴著者' => '伴著者', +'伴著述' => '伴著述', +'伴著錄' => '伴著錄', +'字節' => '位元組', +'字节' => '位元組', +'低著' => '低着', +'低著作' => '低著作', +'低著名' => '低著名', +'低著書' => '低著書', +'低著稱' => '低著稱', +'低著者' => '低著者', +'低著述' => '低著述', +'低著錄' => '低著錄', +'住著' => '住着', +'住著作' => '住著作', +'住著名' => '住著名', +'住著書' => '住著書', +'住著稱' => '住著稱', +'住著者' => '住著者', +'住著述' => '住著述', +'住著錄' => '住著錄', +'維德角' => '佛得角', +'來著' => '來着', +'來著作' => '來著作', +'來著名' => '來著名', +'來著書' => '來著書', +'來著稱' => '來著稱', +'來著者' => '來著者', +'來著述' => '來著述', +'來著錄' => '來著錄', +'海珊' => '侯賽因', +'保障著' => '保障着', +'保障著作' => '保障著作', +'保障著名' => '保障著名', +'保障著書' => '保障著書', +'保障著稱' => '保障著稱', +'保障著者' => '保障著者', +'保障著述' => '保障著述', +'保障著錄' => '保障著錄', +'信著' => '信着', +'信著作' => '信著作', +'信著名' => '信著名', +'信著書' => '信著書', +'信著稱' => '信著稱', +'信著者' => '信著者', +'信著述' => '信著述', +'信著錄' => '信著錄', +'候著' => '候着', +'候著作' => '候著作', +'候著名' => '候著名', +'候著書' => '候著書', +'候著稱' => '候著稱', +'候著者' => '候著者', +'候著述' => '候著述', +'候著錄' => '候著錄', +'借著' => '借着', +'借著作' => '借著作', +'借著名' => '借著名', +'借著書' => '借著書', +'借著稱' => '借著稱', +'借著者' => '借著者', +'借著述' => '借著述', +'借著錄' => '借著錄', +'做著' => '做着', +'做著作' => '做著作', +'做著名' => '做著名', +'做著書' => '做著書', +'做著稱' => '做著稱', +'做著者' => '做著者', +'做著述' => '做著述', +'做著錄' => '做著錄', +'側著' => '側着', +'側著作' => '側著作', +'側著名' => '側著名', +'側著書' => '側著書', +'側著稱' => '側著稱', +'側著者' => '側著者', +'側著述' => '側著述', +'側著錄' => '側著錄', +'偷著' => '偷着', +'偷著作' => '偷著作', +'偷著名' => '偷著名', +'偷著書' => '偷著書', +'偷著稱' => '偷著稱', +'偷著者' => '偷著者', +'偷著述' => '偷著述', +'偷著錄' => '偷著錄', +'備著' => '備着', +'備著作' => '備著作', +'備著名' => '備著名', +'備著書' => '備著書', +'備著稱' => '備著稱', +'備著者' => '備著者', +'備著述' => '備著述', +'備著錄' => '備著錄', +'凶殘' => '兇殘', +'凶殺' => '兇殺', +'雪铁龙' => '先進', +'雪鐵龍' => '先進', +'光著' => '光着', +'光著作' => '光著作', +'光著名' => '光著名', +'光著書' => '光著書', +'光著稱' => '光著稱', +'光著者' => '光著者', +'光著述' => '光著述', +'光著錄' => '光著錄', +'柯林頓' => '克林頓', +'克羅埃西亞' => '克羅地亞', +'公車上書' => '公車上書', +'冀著' => '冀着', +'冀著作' => '冀著作', +'冀著名' => '冀著名', +'冀著書' => '冀著書', +'冀著稱' => '冀著稱', +'冀著者' => '冀著者', +'冀著述' => '冀著述', +'冀著錄' => '冀著錄', +'冒著' => '冒着', +'冒著作' => '冒著作', +'冒著名' => '冒著名', +'冒著書' => '冒著書', +'冒著稱' => '冒著稱', +'冒著者' => '冒著者', +'冒著述' => '冒著述', +'冒著錄' => '冒著錄', +'列支敦斯登' => '列支敦士登', +'賴比瑞亞' => '利比里亞', +'制著' => '制着', +'制著作' => '制著作', +'制著名' => '制著名', +'制著書' => '制著書', +'制著稱' => '制著稱', +'制著者' => '制著者', +'制著述' => '制著述', +'制著錄' => '制著錄', +'刻著' => '刻着', +'刻著作' => '刻著作', +'刻著名' => '刻著名', +'刻著書' => '刻著書', +'刻著稱' => '刻著稱', +'刻著者' => '刻著者', +'刻著述' => '刻著述', +'刻著錄' => '刻著錄', +'迦納' => '加納', +'加彭' => '加蓬', +'努力著' => '努力着', +'努力著作' => '努力著作', +'努力著名' => '努力著名', +'努力著書' => '努力著書', +'努力著稱' => '努力著稱', +'努力著者' => '努力著者', +'努力著述' => '努力著述', +'努力著錄' => '努力著錄', +'努著' => '努着', +'努著作' => '努著作', +'努著名' => '努著名', +'努著書' => '努著書', +'努著稱' => '努著稱', +'努著者' => '努著者', +'努著述' => '努著述', +'努著錄' => '努著錄', +'動著' => '動着', +'動著作' => '動著作', +'動著名' => '動著名', +'動著書' => '動著書', +'動著稱' => '動著稱', +'動著者' => '動著者', +'動著述' => '動著述', +'動著錄' => '動著錄', +'波札那' => '博茨瓦納', +'珍妮弗·卡普里亚蒂' => '卡佩雅蒂', +'印著' => '印着', +'印著作' => '印著作', +'印著名' => '印著名', +'印著書' => '印著書', +'印著稱' => '印著稱', +'印著者' => '印著者', +'印著述' => '印著述', +'印著錄' => '印著錄', +'瓜地馬拉' => '危地馬拉', +'泡麵' => '即食麵', +'方便面' => '即食麵', +'快速面' => '即食麵', +'速食麵' => '即食麵', +'厄瓜多' => '厄瓜多爾', +'厄瓜多爾' => '厄瓜多爾', +'厄瓜多尔' => '厄瓜多爾', +'厄利垂亞' => '厄立特里亞', +'去著' => '去着', +'去著作' => '去著作', +'去著名' => '去著名', +'去著書' => '去著書', +'去著稱' => '去著稱', +'去著者' => '去著者', +'去著述' => '去著述', +'去著錄' => '去著錄', +'受著' => '受着', +'受著作' => '受著作', +'受著名' => '受著名', +'受著書' => '受著書', +'受著稱' => '受著稱', +'受著者' => '受著者', +'受著述' => '受著述', +'受著錄' => '受著錄', +'叫著' => '叫着', +'叫著作' => '叫著作', +'叫著名' => '叫著名', +'叫著書' => '叫著書', +'叫著稱' => '叫著稱', +'叫著者' => '叫著者', +'叫著述' => '叫著述', +'叫著錄' => '叫著錄', +'吉布地' => '吉布堤', +'向著' => '向着', +'向著作' => '向著作', +'向著名' => '向著名', +'向著書' => '向著書', +'向著稱' => '向著稱', +'向著者' => '向著者', +'向著述' => '向著述', +'向著錄' => '向著錄', +'含著' => '含着', +'含著作' => '含著作', +'含著名' => '含著名', +'含著書' => '含著書', +'含著稱' => '含著稱', +'含著者' => '含著者', +'含著述' => '含著述', +'含著錄' => '含著錄', +'吹著' => '吹着', +'吹著作' => '吹著作', +'吹著名' => '吹著名', +'吹著書' => '吹著書', +'吹著稱' => '吹著稱', +'吹著者' => '吹著者', +'吹著述' => '吹著述', +'吹著錄' => '吹著錄', +'味著' => '味着', +'味著作' => '味著作', +'味著名' => '味著名', +'味著書' => '味著書', +'味著稱' => '味著稱', +'味著者' => '味著者', +'味著述' => '味著述', +'味著錄' => '味著錄', +'哥斯大黎加' => '哥斯達黎加', +'哭著' => '哭着', +'哭著作' => '哭著作', +'哭著名' => '哭著名', +'哭著書' => '哭著書', +'哭著稱' => '哭著稱', +'哭著者' => '哭著者', +'哭著述' => '哭著述', +'哭著錄' => '哭著錄', +'唱著' => '唱着', +'唱著作' => '唱著作', +'唱著名' => '唱著名', +'唱著書' => '唱著書', +'唱著稱' => '唱著稱', +'唱著者' => '唱著者', +'唱著述' => '唱著述', +'唱著錄' => '唱著錄', +'喝著' => '喝着', +'喝著作' => '喝著作', +'喝著名' => '喝著名', +'喝著書' => '喝著書', +'喝著稱' => '喝著稱', +'喝著者' => '喝著者', +'喝著述' => '喝著述', +'喝著錄' => '喝著錄', +'自行车' => '單車', +'嚷著' => '嚷着', +'嚷著作' => '嚷著作', +'嚷著名' => '嚷著名', +'嚷著書' => '嚷著書', +'嚷著稱' => '嚷著稱', +'嚷著者' => '嚷著者', +'嚷著述' => '嚷著述', +'嚷著錄' => '嚷著錄', +'因著' => '因着', +'因著作' => '因著作', +'因著名' => '因著名', +'因著書' => '因著書', +'因著稱' => '因著稱', +'因著者' => '因著者', +'因著述' => '因著述', +'因著錄' => '因著錄', +'困著' => '困着', +'困著作' => '困著作', +'困著名' => '困著名', +'困著書' => '困著書', +'困著稱' => '困著稱', +'困著者' => '困著者', +'困著述' => '困著述', +'困著錄' => '困著錄', +'圍著' => '圍着', +'圍著作' => '圍著作', +'圍著名' => '圍著名', +'圍著書' => '圍著書', +'圍著稱' => '圍著稱', +'圍著者' => '圍著者', +'圍著述' => '圍著述', +'圍著錄' => '圍著錄', +'吐瓦魯' => '圖瓦盧', +'土豆網' => '土豆網', +'土豆网' => '土豆網', +'在著' => '在着', +'在著作' => '在著作', +'在著名' => '在著名', +'在著書' => '在著書', +'在著稱' => '在著稱', +'在著者' => '在著者', +'在著述' => '在著述', +'在著錄' => '在著錄', +'蓋亞那' => '圭亞那', +'坐著' => '坐着', +'坐著作' => '坐著作', +'坐著名' => '坐著名', +'坐著書' => '坐著書', +'坐著稱' => '坐著稱', +'坐著者' => '坐著者', +'坐著述' => '坐著述', +'坐著錄' => '坐著錄', +'坦尚尼亞' => '坦桑尼亞', +'衣索匹亞' => '埃塞俄比亞', +'衣索比亞' => '埃塞俄比亞', +'吉里巴斯' => '基里巴斯', +'塞普勒斯' => '塞浦路斯', +'塞席爾' => '塞舌爾', +'壓著' => '壓着', +'壓著作' => '壓著作', +'壓著名' => '壓著名', +'壓著書' => '壓著書', +'壓著稱' => '壓著稱', +'壓著者' => '壓著者', +'壓著述' => '壓著述', +'壓著錄' => '壓著錄', +'夢著' => '夢着', +'夢著作' => '夢著作', +'夢著名' => '夢著名', +'夢著書' => '夢著書', +'夢著稱' => '夢著稱', +'夢著者' => '夢著者', +'夢著述' => '夢著述', +'夢著錄' => '夢著錄', +'大卫·贝克汉姆' => '大衛碧咸', +'夾著' => '夾着', +'夾著作' => '夾著作', +'夾著名' => '夾著名', +'夾著書' => '夾著書', +'夾著稱' => '夾著稱', +'夾著者' => '夾著者', +'夾著述' => '夾著述', +'夾著錄' => '夾著錄', +'孤著' => '孤着', +'孤著作' => '孤著作', +'孤著名' => '孤著名', +'孤著書' => '孤著書', +'孤著稱' => '孤著稱', +'孤著者' => '孤著者', +'孤著述' => '孤著述', +'孤著錄' => '孤著錄', +'學著' => '學着', +'學著作' => '學著作', +'學著名' => '學著名', +'學著書' => '學著書', +'學著稱' => '學著稱', +'學著者' => '學著者', +'學著述' => '學著述', +'學著錄' => '學著錄', +'守著' => '守着', +'守著作' => '守著作', +'守著名' => '守著名', +'守著書' => '守著書', +'守著稱' => '守著稱', +'守著者' => '守著者', +'守著述' => '守著述', +'守著錄' => '守著錄', +'安地卡及巴布達' => '安提瓜和巴布達', +'定著' => '定着', +'定著作' => '定著作', +'定著名' => '定著名', +'定著書' => '定著書', +'定著稱' => '定著稱', +'定著者' => '定著者', +'定著述' => '定著述', +'定著錄' => '定著錄', +'沃尓沃' => '富豪', +'寫著' => '寫着', +'寫著作' => '寫著作', +'寫著名' => '寫著名', +'寫著書' => '寫著書', +'寫著稱' => '寫著稱', +'寫著者' => '寫著者', +'寫著述' => '寫著述', +'寫著錄' => '寫著錄', +'尋著' => '尋着', +'尋著作' => '尋著作', +'尋著名' => '尋著名', +'尋著書' => '尋著書', +'尋著稱' => '尋著稱', +'尋著者' => '尋著者', +'尋著述' => '尋著述', +'尋著錄' => '尋著錄', +'對著' => '對着', +'對著作' => '對著作', +'對著名' => '對著名', +'對著書' => '對著書', +'對著稱' => '對著稱', +'對著者' => '對著者', +'對著述' => '對著述', +'對著錄' => '對著錄', +'奈及利亞' => '尼日利亞', +'尼日利亚' => '尼日利亞', +'尼日利亞' => '尼日利亞', +'尼日尔' => '尼日爾', +'尼日爾' => '尼日爾', +'尼日' => '尼日爾', +'展著' => '展着', +'展著作' => '展著作', +'展著名' => '展著名', +'展著書' => '展著書', +'展著稱' => '展著稱', +'展著者' => '展著者', +'展著述' => '展著述', +'展著錄' => '展著錄', +'甘比亞' => '岡比亞', +'公車' => '巴士', +'巴貝多' => '巴巴多斯', +'巴布亞紐幾內亞' => '巴布亞新畿內亞', +'布吉納法索' => '布基納法索', +'布希' => '布殊', +'布什' => '布殊', +'蒲隆地' => '布隆迪', +'希特勒' => '希特拉', +'帕劳' => '帛琉', +'帶著' => '帶着', +'帶著作' => '帶著作', +'帶著名' => '帶著名', +'帶著書' => '帶著書', +'帶著稱' => '帶著稱', +'帶著者' => '帶著者', +'帶著述' => '帶著述', +'帶著錄' => '帶著錄', +'幫著' => '幫着', +'幫著作' => '幫著作', +'幫著名' => '幫著名', +'幫著書' => '幫著書', +'幫著稱' => '幫著稱', +'幫著者' => '幫著者', +'幫著述' => '幫著述', +'幫著錄' => '幫著錄', +'賓士' => '平治', +'幾內亞比索' => '幾內亞比紹', +'康著' => '康着', +'康著作' => '康著作', +'康著名' => '康著名', +'康著書' => '康著書', +'康著稱' => '康著稱', +'康著者' => '康著者', +'康著述' => '康著述', +'康著錄' => '康著錄', +'待著' => '待着', +'待著作' => '待著作', +'待著名' => '待著名', +'待著書' => '待著書', +'待著稱' => '待著稱', +'待著者' => '待著者', +'待著述' => '待著述', +'待著錄' => '待著錄', +'得著' => '得着', +'得著作' => '得著作', +'得著名' => '得著名', +'得著書' => '得著書', +'得著稱' => '得著稱', +'得著者' => '得著者', +'得著述' => '得著述', +'得著錄' => '得著錄', +'循著' => '循着', +'循著作' => '循著作', +'循著名' => '循著名', +'循著書' => '循著書', +'循著稱' => '循著稱', +'循著者' => '循著者', +'循著述' => '循著述', +'循著錄' => '循著錄', +'心著' => '心着', +'心著作' => '心著作', +'心著名' => '心著名', +'心著書' => '心著書', +'心著稱' => '心著稱', +'心著者' => '心著者', +'心著述' => '心著述', +'心著錄' => '心著錄', +'忍著' => '忍着', +'忍著作' => '忍著作', +'忍著名' => '忍著名', +'忍著書' => '忍著書', +'忍著稱' => '忍著稱', +'忍著者' => '忍著者', +'忍著述' => '忍著述', +'忍著錄' => '忍著錄', +'志著' => '志着', +'志著作' => '志著作', +'志著名' => '志著名', +'志著書' => '志著書', +'志著稱' => '志著稱', +'志著者' => '志著者', +'志著述' => '志著述', +'志著錄' => '志著錄', +'忙著' => '忙着', +'忙著作' => '忙著作', +'忙著名' => '忙著名', +'忙著書' => '忙著書', +'忙著稱' => '忙著稱', +'忙著者' => '忙著者', +'忙著述' => '忙著述', +'忙著錄' => '忙著錄', +'急著' => '急着', +'急著作' => '急著作', +'急著名' => '急著名', +'急著書' => '急著書', +'急著稱' => '急著稱', +'急著者' => '急著者', +'急著述' => '急著述', +'急著錄' => '急著錄', +'性著' => '性着', +'性著作' => '性著作', +'性著名' => '性著名', +'性著書' => '性著書', +'性著稱' => '性著稱', +'性著者' => '性著者', +'性著述' => '性著述', +'性著錄' => '性著錄', +'悠著' => '悠着', +'悠著作' => '悠著作', +'悠著名' => '悠著名', +'悠著書' => '悠著書', +'悠著稱' => '悠著稱', +'悠著者' => '悠著者', +'悠著述' => '悠著述', +'悠著錄' => '悠著錄', +'想著' => '想着', +'想著作' => '想著作', +'想著名' => '想著名', +'想著書' => '想著書', +'想著稱' => '想著稱', +'想著者' => '想著者', +'想著述' => '想著述', +'想著錄' => '想著錄', +'義大利' => '意大利', +'愛著' => '愛着', +'愛著作' => '愛著作', +'愛著名' => '愛著名', +'愛著書' => '愛著書', +'愛著稱' => '愛著稱', +'愛著者' => '愛著者', +'愛著述' => '愛著述', +'愛著錄' => '愛著錄', +'慣著' => '慣着', +'慣著作' => '慣著作', +'慣著名' => '慣著名', +'慣著書' => '慣著書', +'慣著稱' => '慣著稱', +'慣著者' => '慣著者', +'慣著述' => '慣著述', +'慣著錄' => '慣著錄', +'應著' => '應着', +'應著作' => '應著作', +'應著名' => '應著名', +'應著書' => '應著書', +'應著稱' => '應著稱', +'應著者' => '應著者', +'應著述' => '應著述', +'應著錄' => '應著錄', +'懷著' => '懷着', +'懷著作' => '懷著作', +'懷著名' => '懷著名', +'懷著書' => '懷著書', +'懷著稱' => '懷著稱', +'懷著者' => '懷著者', +'懷著述' => '懷著述', +'懷著錄' => '懷著錄', +'戀著' => '戀着', +'戀著作' => '戀著作', +'戀著名' => '戀著名', +'戀著書' => '戀著書', +'戀著稱' => '戀著稱', +'戀著者' => '戀著者', +'戀著述' => '戀著述', +'戀著錄' => '戀著錄', +'戰著' => '戰着', +'戰著作' => '戰著作', +'戰著名' => '戰著名', +'戰著書' => '戰著書', +'戰著稱' => '戰著稱', +'戰著者' => '戰著者', +'戰著述' => '戰著述', +'戰著錄' => '戰著錄', +'黛安娜' => '戴安娜', +'狄安娜' => '戴安娜', +'戴著' => '戴着', +'戴著作' => '戴著作', +'戴著名' => '戴著名', +'戴著書' => '戴著書', +'戴著稱' => '戴著稱', +'戴著者' => '戴著者', +'戴著述' => '戴著述', +'戴著錄' => '戴著錄', +'索羅門群島' => '所羅門群島', +'列印' => '打印', +'印表機' => '打印機', +'打著' => '打着', +'打著作' => '打著作', +'打著名' => '打著名', +'打著書' => '打著書', +'打著稱' => '打著稱', +'打著者' => '打著者', +'打著述' => '打著述', +'打著錄' => '打著錄', +'扛著' => '扛着', +'扛著作' => '扛著作', +'扛著名' => '扛著名', +'扛著書' => '扛著書', +'扛著稱' => '扛著稱', +'扛著者' => '扛著者', +'扛著述' => '扛著述', +'扛著錄' => '扛著錄', +'找不著' => '找不着', +'找不著作' => '找不著作', +'找不著名' => '找不著名', +'找不著書' => '找不著書', +'找不著稱' => '找不著稱', +'找不著者' => '找不著者', +'找不著述' => '找不著述', +'找不著錄' => '找不著錄', +'抓著' => '抓着', +'抓著作' => '抓著作', +'抓著名' => '抓著名', +'抓著書' => '抓著書', +'抓著稱' => '抓著稱', +'抓著者' => '抓著者', +'抓著述' => '抓著述', +'抓著錄' => '抓著錄', +'披著' => '披着', +'披著作' => '披著作', +'披著名' => '披著名', +'披著書' => '披著書', +'披著稱' => '披著稱', +'披著者' => '披著者', +'披著述' => '披著述', +'披著錄' => '披著錄', +'抬著' => '抬着', +'抬著作' => '抬著作', +'抬著名' => '抬著名', +'抬著書' => '抬著書', +'抬著稱' => '抬著稱', +'抬著者' => '抬著者', +'抬著述' => '抬著述', +'抬著錄' => '抬著錄', +'抱著' => '抱着', +'抱著作' => '抱著作', +'抱著名' => '抱著名', +'抱著書' => '抱著書', +'抱著稱' => '抱著稱', +'抱著者' => '抱著者', +'抱著述' => '抱著述', +'抱著錄' => '抱著錄', +'拉著' => '拉着', +'拉著作' => '拉著作', +'拉著名' => '拉著名', +'拉著書' => '拉著書', +'拉著稱' => '拉著稱', +'拉著者' => '拉著者', +'拉著述' => '拉著述', +'拉著錄' => '拉著錄', +'拎著' => '拎着', +'拎著作' => '拎著作', +'拎著名' => '拎著名', +'拎著書' => '拎著書', +'拎著稱' => '拎著稱', +'拎著者' => '拎著者', +'拎著述' => '拎著述', +'拎著錄' => '拎著錄', +'拖著' => '拖着', +'拖著作' => '拖著作', +'拖著名' => '拖著名', +'拖著書' => '拖著書', +'拖著稱' => '拖著稱', +'拖著者' => '拖著者', +'拖著述' => '拖著述', +'拖著錄' => '拖著錄', +'拼著' => '拼着', +'拼著作' => '拼著作', +'拼著名' => '拼著名', +'拼著書' => '拼著書', +'拼著稱' => '拼著稱', +'拼著者' => '拼著者', +'拼著述' => '拼著述', +'拼著錄' => '拼著錄', +'拿著' => '拿着', +'拿破崙' => '拿破侖', +'拿著作' => '拿著作', +'拿著名' => '拿著名', +'拿著書' => '拿著書', +'拿著稱' => '拿著稱', +'拿著者' => '拿著者', +'拿著述' => '拿著述', +'拿著錄' => '拿著錄', +'持著' => '持着', +'持著作' => '持著作', +'持著名' => '持著名', +'持著書' => '持著書', +'持著稱' => '持著稱', +'持著者' => '持著者', +'持著述' => '持著述', +'持著錄' => '持著錄', +'挑著' => '挑着', +'挑著作' => '挑著作', +'挑著名' => '挑著名', +'挑著書' => '挑著書', +'挑著稱' => '挑著稱', +'挑著者' => '挑著者', +'挑著述' => '挑著述', +'挑著錄' => '挑著錄', +'挨著' => '挨着', +'挨著作' => '挨著作', +'挨著名' => '挨著名', +'挨著書' => '挨著書', +'挨著稱' => '挨著稱', +'挨著者' => '挨著者', +'挨著述' => '挨著述', +'挨著錄' => '挨著錄', +'捆著' => '捆着', +'捆著作' => '捆著作', +'捆著名' => '捆著名', +'捆著書' => '捆著書', +'捆著稱' => '捆著稱', +'捆著者' => '捆著者', +'捆著述' => '捆著述', +'捆著錄' => '捆著錄', +'掖著' => '掖着', +'掖著作' => '掖著作', +'掖著名' => '掖著名', +'掖著書' => '掖著書', +'掖著稱' => '掖著稱', +'掖著者' => '掖著者', +'掖著述' => '掖著述', +'掖著錄' => '掖著錄', +'掙著' => '掙着', +'掙著作' => '掙著作', +'掙著名' => '掙著名', +'掙著書' => '掙著書', +'掙著稱' => '掙著稱', +'掙著者' => '掙著者', +'掙著述' => '掙著述', +'掙著錄' => '掙著錄', +'接著' => '接着', +'接著作' => '接著作', +'接著名' => '接著名', +'接著書' => '接著書', +'接著稱' => '接著稱', +'接著者' => '接著者', +'接著述' => '接著述', +'接著錄' => '接著錄', +'揉著' => '揉着', +'揉著作' => '揉著作', +'揉著名' => '揉著名', +'揉著書' => '揉著書', +'揉著稱' => '揉著稱', +'揉著者' => '揉著者', +'揉著述' => '揉著述', +'揉著錄' => '揉著錄', +'提著' => '提着', +'提著作' => '提著作', +'提著名' => '提著名', +'提著書' => '提著書', +'提著稱' => '提著稱', +'提著者' => '提著者', +'提著述' => '提著述', +'提著錄' => '提著錄', +'揮著' => '揮着', +'揮著作' => '揮著作', +'揮著名' => '揮著名', +'揮著書' => '揮著書', +'揮著稱' => '揮著稱', +'揮著者' => '揮著者', +'揮著述' => '揮著述', +'揮著錄' => '揮著錄', +'摟著' => '摟着', +'摟著作' => '摟著作', +'摟著名' => '摟著名', +'摟著書' => '摟著書', +'摟著稱' => '摟著稱', +'摟著者' => '摟著者', +'摟著述' => '摟著述', +'摟著錄' => '摟著錄', +'撼著' => '撼着', +'撼著作' => '撼著作', +'撼著名' => '撼著名', +'撼著書' => '撼著書', +'撼著稱' => '撼著稱', +'撼著者' => '撼著者', +'撼著述' => '撼著述', +'撼著錄' => '撼著錄', +'擋著' => '擋着', +'擋著作' => '擋著作', +'擋著名' => '擋著名', +'擋著書' => '擋著書', +'擋著稱' => '擋著稱', +'擋著者' => '擋著者', +'擋著述' => '擋著述', +'擋著錄' => '擋著錄', +'據著' => '據着', +'據著作' => '據著作', +'據著名' => '據著名', +'據著書' => '據著書', +'據著稱' => '據著稱', +'據著者' => '據著者', +'據著述' => '據著述', +'據著錄' => '據著錄', +'擺著' => '擺着', +'擺著作' => '擺著作', +'擺著名' => '擺著名', +'擺著書' => '擺著書', +'擺著稱' => '擺著稱', +'擺著者' => '擺著者', +'擺著述' => '擺著述', +'擺著錄' => '擺著錄', +'敞著' => '敞着', +'敞著作' => '敞著作', +'敞著名' => '敞著名', +'敞著書' => '敞著書', +'敞著稱' => '敞著稱', +'敞著者' => '敞著者', +'敞著述' => '敞著述', +'敞著錄' => '敞著錄', +'數著' => '數着', +'數著作' => '數著作', +'數著名' => '數著名', +'數著書' => '數著書', +'數著稱' => '數著稱', +'數著者' => '數著者', +'數著述' => '數著述', +'數著錄' => '數著錄', +'斥著' => '斥着', +'斥著作' => '斥著作', +'斥著名' => '斥著名', +'斥著書' => '斥著書', +'斥著稱' => '斥著稱', +'斥著者' => '斥著者', +'斥著述' => '斥著述', +'斥著錄' => '斥著錄', +'史瓦濟蘭' => '斯威士蘭', +'斯洛維尼亞' => '斯洛文尼亞', +'新著龍虎門' => '新著龍虎門', +'紐西蘭' => '新西蘭', +'昂著' => '昂着', +'昂著作' => '昂著作', +'昂著名' => '昂著名', +'昂著書' => '昂著書', +'昂著稱' => '昂著稱', +'昂著者' => '昂著者', +'昂著述' => '昂著述', +'昂著錄' => '昂著錄', +'映著' => '映着', +'映著作' => '映著作', +'映著名' => '映著名', +'映著書' => '映著書', +'映著稱' => '映著稱', +'映著者' => '映著者', +'映著述' => '映著述', +'映著錄' => '映著錄', +'晃著' => '晃着', +'晃著作' => '晃著作', +'晃著名' => '晃著名', +'晃著書' => '晃著書', +'晃著稱' => '晃著稱', +'晃著者' => '晃著者', +'晃著述' => '晃著述', +'晃著錄' => '晃著錄', +'暗著' => '暗着', +'暗著作' => '暗著作', +'暗著名' => '暗著名', +'暗著書' => '暗著書', +'暗著稱' => '暗著稱', +'暗著者' => '暗著者', +'暗著述' => '暗著述', +'暗著錄' => '暗著錄', +'有著' => '有着', +'有著作' => '有著作', +'有著名' => '有著名', +'有著書' => '有著書', +'有著稱' => '有著稱', +'有著者' => '有著者', +'有著述' => '有著述', +'有著錄' => '有著錄', +'望著' => '望着', +'望著作' => '望著作', +'望著名' => '望著名', +'望著書' => '望著書', +'望著稱' => '望著稱', +'望著者' => '望著者', +'望著述' => '望著述', +'望著錄' => '望著錄', +'朝著' => '朝着', +'朝著作' => '朝著作', +'朝著名' => '朝著名', +'朝著書' => '朝著書', +'朝著稱' => '朝著稱', +'朝著者' => '朝著者', +'朝著述' => '朝著述', +'朝著錄' => '朝著錄', +'本著' => '本着', +'本著作' => '本著作', +'本著名' => '本著名', +'本著書' => '本著書', +'本著稱' => '本著稱', +'本著者' => '本著者', +'本著述' => '本著述', +'本著錄' => '本著錄', +'枕著' => '枕着', +'枕著作' => '枕著作', +'枕著名' => '枕著名', +'枕著書' => '枕著書', +'枕著稱' => '枕著稱', +'枕著者' => '枕著者', +'枕著述' => '枕著述', +'枕著錄' => '枕著錄', +'格瑞那達' => '格林納達', +'撞球' => '桌球', +'台球' => '桌球', +'梳著' => '梳着', +'梳著作' => '梳著作', +'梳著名' => '梳著名', +'梳著書' => '梳著書', +'梳著稱' => '梳著稱', +'梳著者' => '梳著者', +'梳著述' => '梳著述', +'梳著錄' => '梳著錄', +'榴蓮' => '榴槤', +'榴莲' => '榴槤', +'樂著' => '樂着', +'樂著作' => '樂著作', +'樂著名' => '樂著名', +'樂著書' => '樂著書', +'樂著稱' => '樂著稱', +'樂著者' => '樂著者', +'樂著述' => '樂著述', +'樂著錄' => '樂著錄', +'寶獅' => '標致', +'機器人' => '機械人', +'机器人' => '機械人', +'殺著' => '殺着', +'殺著作' => '殺著作', +'殺著名' => '殺著名', +'殺著書' => '殺著書', +'殺著稱' => '殺著稱', +'殺著者' => '殺著者', +'殺著述' => '殺著述', +'殺著錄' => '殺著錄', +'茅利塔尼亞' => '毛里塔尼亞', +'毛里求斯' => '毛里裘斯', +'模里西斯' => '毛里裘斯', +'求著' => '求着', +'求著作' => '求著作', +'求著名' => '求著名', +'求著書' => '求著書', +'求著稱' => '求著稱', +'求著者' => '求著者', +'求著述' => '求著述', +'求著錄' => '求著錄', +'文莱' => '汶萊', +'沉著' => '沉着', +'沉著作' => '沉著作', +'沉著名' => '沉著名', +'沉著書' => '沉著書', +'沉著稱' => '沉著稱', +'沉著者' => '沉著者', +'沉著述' => '沉著述', +'沉著錄' => '沉著錄', +'沙地阿拉伯' => '沙特阿拉伯', +'沙烏地阿拉伯' => '沙特阿拉伯', +'马拉特·萨芬' => '沙芬', +'沿著' => '沿着', +'沿著作' => '沿著作', +'沿著名' => '沿著名', +'沿著書' => '沿著書', +'沿著稱' => '沿著稱', +'沿著者' => '沿著者', +'沿著述' => '沿著述', +'沿著錄' => '沿著錄', +'波士尼亞赫塞哥維納' => '波斯尼亞黑塞哥維那', +'辛巴威' => '津巴布韋', +'宏都拉斯' => '洪都拉斯', +'活著' => '活着', +'活著作' => '活著作', +'活著名' => '活著名', +'活著書' => '活著書', +'活著稱' => '活著稱', +'活著者' => '活著者', +'活著述' => '活著述', +'活著錄' => '活著錄', +'行動電話' => '流動電話', +'移动电话' => '流動電話', +'流著' => '流着', +'流著作' => '流著作', +'流著名' => '流著名', +'流著書' => '流著書', +'流著稱' => '流著稱', +'流著者' => '流著者', +'流著述' => '流著述', +'流著錄' => '流著錄', +'浮著' => '浮着', +'浮著作' => '浮著作', +'浮著名' => '浮著名', +'浮著書' => '浮著書', +'浮著稱' => '浮著稱', +'浮著者' => '浮著者', +'浮著述' => '浮著述', +'浮著錄' => '浮著錄', +'涵著' => '涵着', +'涵著作' => '涵著作', +'涵著名' => '涵著名', +'涵著書' => '涵著書', +'涵著稱' => '涵著稱', +'涵著者' => '涵著者', +'涵著述' => '涵著述', +'涵著錄' => '涵著錄', +'涼著' => '涼着', +'涼著作' => '涼著作', +'涼著名' => '涼著名', +'涼著書' => '涼著書', +'涼著稱' => '涼著稱', +'涼著者' => '涼著者', +'涼著述' => '涼著述', +'涼著錄' => '涼著錄', +'渴著' => '渴着', +'渴著作' => '渴著作', +'渴著名' => '渴著名', +'渴著書' => '渴著書', +'渴著稱' => '渴著稱', +'渴著者' => '渴著者', +'渴著述' => '渴著述', +'渴著錄' => '渴著錄', +'溢著' => '溢着', +'溢著作' => '溢著作', +'溢著名' => '溢著名', +'溢著書' => '溢著書', +'溢著稱' => '溢著稱', +'溢著者' => '溢著者', +'溢著述' => '溢著述', +'溢著錄' => '溢著錄', +'演著' => '演着', +'演著作' => '演著作', +'演著名' => '演著名', +'演著書' => '演著書', +'演著稱' => '演著稱', +'演著者' => '演著者', +'演著述' => '演著述', +'演著錄' => '演著錄', +'漫著' => '漫着', +'漫著作' => '漫著作', +'漫著名' => '漫著名', +'漫著書' => '漫著書', +'漫著稱' => '漫著稱', +'漫著者' => '漫著者', +'漫著述' => '漫著述', +'漫著錄' => '漫著錄', +'潤著' => '潤着', +'潤著作' => '潤著作', +'潤著名' => '潤著名', +'潤著書' => '潤著書', +'潤著稱' => '潤著稱', +'潤著者' => '潤著者', +'潤著述' => '潤著述', +'潤著錄' => '潤著錄', +'照著' => '照着', +'照著作' => '照著作', +'照著名' => '照著名', +'照著書' => '照著書', +'照著稱' => '照著稱', +'照著者' => '照著者', +'照著述' => '照著述', +'照著錄' => '照著錄', +'燒著' => '燒着', +'燒著作' => '燒著作', +'燒著名' => '燒著名', +'燒著書' => '燒著書', +'燒著稱' => '燒著稱', +'燒著者' => '燒著者', +'燒著述' => '燒著述', +'燒著錄' => '燒著錄', +'爭著' => '爭着', +'爭著作' => '爭著作', +'爭著名' => '爭著名', +'爭著書' => '爭著書', +'爭著稱' => '爭著稱', +'爭著者' => '爭著者', +'爭著述' => '爭著述', +'爭著錄' => '爭著錄', +'千里達托貝哥' => '特立尼達和多巴哥', +'牽著' => '牽着', +'牽著作' => '牽著作', +'牽著名' => '牽著名', +'牽著書' => '牽著書', +'牽著稱' => '牽著稱', +'牽著者' => '牽著者', +'牽著述' => '牽著述', +'牽著錄' => '牽著錄', +'犯不著' => '犯不着', +'犯不著作' => '犯不著作', +'犯不著名' => '犯不著名', +'犯不著書' => '犯不著書', +'犯不著稱' => '犯不著稱', +'犯不著者' => '犯不著者', +'犯不著述' => '犯不著述', +'犯不著錄' => '犯不著錄', +'犬只' => '狗隻', +'猜著' => '猜着', +'猜著作' => '猜著作', +'猜著名' => '猜著名', +'猜著書' => '猜著書', +'猜著稱' => '猜著稱', +'猜著者' => '猜著者', +'猜著述' => '猜著述', +'猜著錄' => '猜著錄', +'獨著' => '獨着', +'獨著作' => '獨著作', +'獨著名' => '獨著名', +'獨著書' => '獨著書', +'獨著稱' => '獨著稱', +'獨著者' => '獨著者', +'獨著述' => '獨著述', +'獨著錄' => '獨著錄', +'獲著' => '獲着', +'獲著作' => '獲著作', +'獲著名' => '獲著名', +'獲著書' => '獲著書', +'獲著稱' => '獲著稱', +'獲著者' => '獲著者', +'獲著述' => '獲著述', +'獲著錄' => '獲著錄', +'諾魯' => '瑙魯', +'萬那杜' => '瓦努阿圖', +'甜著' => '甜着', +'甜著作' => '甜著作', +'甜著名' => '甜著名', +'甜著書' => '甜著書', +'甜著稱' => '甜著稱', +'甜著者' => '甜著者', +'甜著述' => '甜著述', +'甜著錄' => '甜著錄', +'用不著' => '用不着', +'用不著作' => '用不著作', +'用不著名' => '用不著名', +'用不著書' => '用不著書', +'用不著稱' => '用不著稱', +'用不著者' => '用不著者', +'用不著述' => '用不著述', +'用不著錄' => '用不著錄', +'用著' => '用着', +'用著作' => '用著作', +'用著名' => '用著名', +'用著書' => '用著書', +'用著稱' => '用著稱', +'用著者' => '用著者', +'用著述' => '用著述', +'用著錄' => '用著錄', +'留著' => '留着', +'留著作' => '留著作', +'留著名' => '留著名', +'留著書' => '留著書', +'留著稱' => '留著稱', +'留著者' => '留著者', +'留著述' => '留著述', +'留著錄' => '留著錄', +'當著' => '當着', +'當著作' => '當著作', +'當著名' => '當著名', +'當著書' => '當著書', +'當著稱' => '當著稱', +'當著者' => '當著者', +'當著述' => '當著述', +'當著錄' => '當著錄', +'疑著' => '疑着', +'疑著作' => '疑著作', +'疑著名' => '疑著名', +'疑著書' => '疑著書', +'疑著稱' => '疑著稱', +'疑著者' => '疑著者', +'疑著述' => '疑著述', +'疑著錄' => '疑著錄', +'发布' => '發佈', +'發布' => '發佈', +'計程車' => '的士', +'出租车' => '的士', +'皺著' => '皺着', +'皺著作' => '皺著作', +'皺著名' => '皺著名', +'皺著書' => '皺著書', +'皺著稱' => '皺著稱', +'皺著者' => '皺著者', +'皺著述' => '皺著述', +'皺著錄' => '皺著錄', +'盛著' => '盛着', +'盛著作' => '盛著作', +'盛著名' => '盛著名', +'盛著書' => '盛著書', +'盛著稱' => '盛著稱', +'盛著者' => '盛著者', +'盛著述' => '盛著述', +'盛著錄' => '盛著錄', +'盧安達' => '盧旺達', +'盯著' => '盯着', +'盯著作' => '盯著作', +'盯著名' => '盯著名', +'盯著書' => '盯著書', +'盯著稱' => '盯著稱', +'盯著者' => '盯著者', +'盯著述' => '盯著述', +'盯著錄' => '盯著錄', +'盾著' => '盾着', +'盾著作' => '盾著作', +'盾著名' => '盾著名', +'盾著書' => '盾著書', +'盾著稱' => '盾著稱', +'盾著者' => '盾著者', +'盾著述' => '盾著述', +'盾著錄' => '盾著錄', +'看著' => '看着', +'看著作' => '看著作', +'看著名' => '看著名', +'看著書' => '看著書', +'看著稱' => '看著稱', +'看著者' => '看著者', +'看著述' => '看著述', +'看著錄' => '看著錄', +'著什麼急' => '着什麼急', +'著他' => '着他', +'著你' => '着你', +'著力' => '着力', +'著地' => '着地', +'著墨' => '着墨', +'著她' => '着她', +'著妳' => '着妳', +'著它' => '着它', +'著實' => '着實', +'著忙' => '着忙', +'著急' => '着急', +'著想' => '着想', +'著意' => '着意', +'著我' => '着我', +'著手' => '着手', +'著數' => '着數', +'著法' => '着法', +'著涼' => '着涼', +'著火' => '着火', +'著眼' => '着眼', +'著祂' => '着祂', +'著筆' => '着筆', +'著絲' => '着絲', +'著緊' => '着緊', +'著腳' => '着腳', +'著艦' => '着艦', +'著色' => '着色', +'著落' => '着落', +'著衣' => '着衣', +'著裝' => '着裝', +'著迷' => '着迷', +'著重' => '着重', +'著錄' => '着錄', +'著陸' => '着陸', +'著鞭' => '着鞭', +'睡不著' => '睡不着', +'睡不著作' => '睡不著作', +'睡不著名' => '睡不著名', +'睡不著書' => '睡不著書', +'睡不著稱' => '睡不著稱', +'睡不著者' => '睡不著者', +'睡不著述' => '睡不著述', +'睡不著錄' => '睡不著錄', +'睡著' => '睡着', +'睡著作' => '睡著作', +'睡著名' => '睡著名', +'睡著書' => '睡著書', +'睡著稱' => '睡著稱', +'睡著者' => '睡著者', +'睡著述' => '睡著述', +'睡著錄' => '睡著錄', +'瞞著' => '瞞着', +'瞞著作' => '瞞著作', +'瞞著名' => '瞞著名', +'瞞著書' => '瞞著書', +'瞞著稱' => '瞞著稱', +'瞞著者' => '瞞著者', +'瞞著述' => '瞞著述', +'瞞著錄' => '瞞著錄', +'瞪著' => '瞪着', +'瞪著作' => '瞪著作', +'瞪著名' => '瞪著名', +'瞪著書' => '瞪著書', +'瞪著稱' => '瞪著稱', +'瞪著者' => '瞪著者', +'瞪著述' => '瞪著述', +'瞪著錄' => '瞪著錄', +'簡訊' => '短訊', +'短信' => '短訊', +'硬件' => '硬件', +'硬體' => '硬件', +'福斯' => '福士', +'福著' => '福着', +'福著作' => '福著作', +'福著名' => '福著名', +'福著書' => '福著書', +'福著稱' => '福著稱', +'福著者' => '福著者', +'福著述' => '福著述', +'福著錄' => '福著錄', +'葛摩' => '科摩羅', +'捷豹' => '積架', +'空著' => '空着', +'空著作' => '空著作', +'空著名' => '空著名', +'空著書' => '空著書', +'空著稱' => '空著稱', +'空著者' => '空著者', +'空著述' => '空著述', +'空著錄' => '空著錄', +'太空梭' => '穿梭機', +'航天飞机' => '穿梭機', +'穿著' => '穿着', +'穿著作' => '穿著作', +'穿著名' => '穿著名', +'穿著書' => '穿著書', +'穿著稱' => '穿著稱', +'穿著者' => '穿著者', +'穿著述' => '穿著述', +'穿著錄' => '穿著錄', +'站著' => '站着', +'站著作' => '站著作', +'站著名' => '站著名', +'站著書' => '站著書', +'站著稱' => '站著稱', +'站著者' => '站著者', +'站著述' => '站著述', +'站著錄' => '站著錄', +'笑著' => '笑着', +'笑著作' => '笑著作', +'笑著名' => '笑著名', +'笑著書' => '笑著書', +'笑著稱' => '笑著稱', +'笑著者' => '笑著者', +'笑著述' => '笑著述', +'笑著錄' => '笑著錄', +'管著' => '管着', +'管著作' => '管著作', +'管著名' => '管著名', +'管著書' => '管著書', +'管著稱' => '管著稱', +'管著者' => '管著者', +'管著述' => '管著述', +'管著錄' => '管著錄', +'迈克尔·欧文' => '米高奧雲', +'索馬利亞' => '索馬里', +'紮著' => '紮着', +'紮著作' => '紮著作', +'紮著名' => '紮著名', +'紮著書' => '紮著書', +'紮著稱' => '紮著稱', +'紮著者' => '紮著者', +'紮著述' => '紮著述', +'紮著錄' => '紮著錄', +'綁著' => '綁着', +'綁著作' => '綁著作', +'綁著名' => '綁著名', +'綁著書' => '綁著書', +'綁著稱' => '綁著稱', +'綁著者' => '綁著者', +'綁著述' => '綁著述', +'綁著錄' => '綁著錄', +'網路' => '網絡', +'緝凶' => '緝兇', +'繞著' => '繞着', +'繞著作' => '繞著作', +'繞著名' => '繞著名', +'繞著書' => '繞著書', +'繞著稱' => '繞著稱', +'繞著者' => '繞著者', +'繞著述' => '繞著述', +'繞著錄' => '繞著錄', +'纏著' => '纏着', +'纏著作' => '纏著作', +'纏著名' => '纏著名', +'纏著書' => '纏著書', +'纏著稱' => '纏著稱', +'纏著者' => '纏著者', +'纏著述' => '纏著述', +'纏著錄' => '纏著錄', +'罩著' => '罩着', +'罩著作' => '罩著作', +'罩著名' => '罩著名', +'罩著書' => '罩著書', +'罩著稱' => '罩著稱', +'罩著者' => '罩著者', +'罩著述' => '罩著述', +'罩著錄' => '罩著錄', +'罵著' => '罵着', +'罵著作' => '罵著作', +'罵著名' => '罵著名', +'罵著書' => '罵著書', +'罵著稱' => '罵著稱', +'罵著者' => '罵著者', +'罵著述' => '罵著述', +'罵著錄' => '罵著錄', +'美著' => '美着', +'美著作' => '美著作', +'美著名' => '美著名', +'美著書' => '美著書', +'美著稱' => '美著稱', +'美著者' => '美著者', +'美著述' => '美著述', +'美著錄' => '美著錄', +'耀著' => '耀着', +'耀著作' => '耀著作', +'耀著名' => '耀著名', +'耀著書' => '耀著書', +'耀著稱' => '耀著稱', +'耀著者' => '耀著者', +'耀著述' => '耀著述', +'耀著錄' => '耀著錄', +'寮國' => '老撾', +'考著' => '考着', +'考著作' => '考著作', +'考著名' => '考著名', +'考著書' => '考著書', +'考著稱' => '考著稱', +'考著者' => '考著者', +'考著述' => '考著述', +'考著錄' => '考著錄', +'圣基茨和尼维斯' => '聖吉斯納域斯', +'聖克里斯多福及尼維斯' => '聖吉斯納域斯', +'聖文森及格瑞那丁' => '聖文森特和格林納丁斯', +'聖露西亞' => '聖盧西亞', +'聖馬利諾' => '聖馬力諾', +'聽著' => '聽着', +'聽著作' => '聽著作', +'聽著名' => '聽著名', +'聽著書' => '聽著書', +'聽著稱' => '聽著稱', +'聽著者' => '聽著者', +'聽著述' => '聽著述', +'聽著錄' => '聽著錄', +'肯尼亚' => '肯雅', +'肯亞' => '肯雅', +'背著' => '背着', +'背著作' => '背著作', +'背著名' => '背著名', +'背著書' => '背著書', +'背著稱' => '背著稱', +'背著者' => '背著者', +'背著述' => '背著述', +'背著錄' => '背著錄', +'膠著' => '膠着', +'膠著作' => '膠著作', +'膠著名' => '膠著名', +'膠著書' => '膠著書', +'膠著稱' => '膠著稱', +'膠著者' => '膠著者', +'膠著述' => '膠著述', +'膠著錄' => '膠著錄', +'臨著' => '臨着', +'臨著作' => '臨著作', +'臨著名' => '臨著名', +'臨著書' => '臨著書', +'臨著稱' => '臨著稱', +'臨著者' => '臨著者', +'臨著述' => '臨著述', +'臨著錄' => '臨著錄', +'與著' => '與着', +'與著作' => '與著作', +'與著名' => '與著名', +'與著書' => '與著書', +'與著稱' => '與著稱', +'與著者' => '與著者', +'與著述' => '與著述', +'與著錄' => '與著錄', +'迈克尔·舒马赫' => '舒麥加', +'苦著' => '苦着', +'苦著作' => '苦著作', +'苦著名' => '苦著名', +'苦著書' => '苦著書', +'苦著稱' => '苦著稱', +'苦著者' => '苦著者', +'苦著述' => '苦著述', +'苦著錄' => '苦著錄', +'莫三比克' => '莫桑比克', +'賴索托' => '萊索托', +'馬自達' => '萬事得', +'马自达' => '萬事得', +'落著' => '落着', +'落著作' => '落著作', +'落著名' => '落著名', +'落著書' => '落著書', +'落著稱' => '落著稱', +'落著者' => '落著者', +'落著述' => '落著述', +'落著錄' => '落著錄', +'蒙著' => '蒙着', +'蒙著作' => '蒙著作', +'蒙著名' => '蒙著名', +'蒙著書' => '蒙著書', +'蒙著稱' => '蒙著稱', +'蒙著者' => '蒙著者', +'蒙著述' => '蒙著述', +'蒙著錄' => '蒙著錄', +'萨达姆' => '薩達姆', +'藏著' => '藏着', +'藏著作' => '藏著作', +'藏著名' => '藏著名', +'藏著書' => '藏著書', +'藏著稱' => '藏著稱', +'藏著者' => '藏著者', +'藏著述' => '藏著述', +'藏著錄' => '藏著錄', +'藝著' => '藝着', +'藝著作' => '藝著作', +'藝著名' => '藝著名', +'藝著書' => '藝著書', +'藝著稱' => '藝著稱', +'藝著者' => '藝著者', +'藝著述' => '藝著述', +'藝著錄' => '藝著錄', +'蘸著' => '蘸着', +'蘸著作' => '蘸著作', +'蘸著名' => '蘸著名', +'蘸著書' => '蘸著書', +'蘸著稱' => '蘸著稱', +'蘸著者' => '蘸著者', +'蘸著述' => '蘸著述', +'蘸著錄' => '蘸著錄', +'行著' => '行着', +'行著作' => '行著作', +'行著名' => '行著名', +'行著書' => '行著書', +'行著稱' => '行著稱', +'行著者' => '行著者', +'行著述' => '行著述', +'行著錄' => '行著錄', +'衣著' => '衣着', +'衣著作' => '衣著作', +'衣著名' => '衣著名', +'衣著書' => '衣著書', +'衣著稱' => '衣著稱', +'衣著者' => '衣著者', +'衣著述' => '衣著述', +'衣著錄' => '衣著錄', +'裝著' => '裝着', +'裝著作' => '裝著作', +'裝著名' => '裝著名', +'裝著書' => '裝著書', +'裝著稱' => '裝著稱', +'裝著者' => '裝著者', +'裝著述' => '裝著述', +'裝著錄' => '裝著錄', +'裹著' => '裹着', +'裹著作' => '裹著作', +'裹著名' => '裹著名', +'裹著書' => '裹著書', +'裹著稱' => '裹著稱', +'裹著者' => '裹著者', +'裹著述' => '裹著述', +'裹著錄' => '裹著錄', +'見著' => '見着', +'見著作' => '見著作', +'見著名' => '見著名', +'見著書' => '見著書', +'見著稱' => '見著稱', +'見著者' => '見著者', +'見著述' => '見著述', +'見著錄' => '見著錄', +'記著' => '記着', +'記著作' => '記著作', +'記著名' => '記著名', +'記著書' => '記著書', +'記著稱' => '記著稱', +'記著者' => '記著者', +'記著述' => '記著述', +'記著錄' => '記著錄', +'試著' => '試着', +'試著作' => '試著作', +'試著名' => '試著名', +'試著書' => '試著書', +'試著稱' => '試著稱', +'試著者' => '試著者', +'試著述' => '試著述', +'試著錄' => '試著錄', +'語著' => '語着', +'語著作' => '語著作', +'語著名' => '語著名', +'語著書' => '語著書', +'語著稱' => '語著稱', +'語著者' => '語著者', +'語著述' => '語著述', +'語著錄' => '語著錄', +'數據機' => '調制解調器', +'變著' => '變着', +'變著作' => '變著作', +'變著名' => '變著名', +'變著書' => '變著書', +'變著稱' => '變著稱', +'變著者' => '變著者', +'變著述' => '變著述', +'變著錄' => '變著錄', +'豎著' => '豎着', +'豎著作' => '豎著作', +'豎著名' => '豎著名', +'豎著書' => '豎著書', +'豎著稱' => '豎著稱', +'豎著者' => '豎著者', +'豎著述' => '豎著述', +'豎著錄' => '豎著錄', +'豫著' => '豫着', +'豫著作' => '豫著作', +'豫著名' => '豫著名', +'豫著書' => '豫著書', +'豫著稱' => '豫著稱', +'豫著者' => '豫著者', +'豫著述' => '豫著述', +'豫著錄' => '豫著錄', +'貝南' => '貝寧', +'貞著' => '貞着', +'貞著作' => '貞著作', +'貞著名' => '貞著名', +'貞著書' => '貞著書', +'貞著稱' => '貞著稱', +'貞著者' => '貞著者', +'貞著述' => '貞著述', +'貞著錄' => '貞著錄', +'買凶' => '買兇', +'尚比亞' => '贊比亞', +'走著' => '走着', +'走著作' => '走著作', +'走著名' => '走著名', +'走著書' => '走著書', +'走著稱' => '走著稱', +'走著者' => '走著者', +'走著述' => '走著述', +'走著錄' => '走著錄', +'趕著' => '趕着', +'趕著作' => '趕著作', +'趕著名' => '趕著名', +'趕著書' => '趕著書', +'趕著稱' => '趕著稱', +'趕著者' => '趕著者', +'趕著述' => '趕著述', +'趕著錄' => '趕著錄', +'趴著' => '趴着', +'趴著作' => '趴著作', +'趴著名' => '趴著名', +'趴著書' => '趴著書', +'趴著稱' => '趴著稱', +'趴著者' => '趴著者', +'趴著述' => '趴著述', +'趴著錄' => '趴著錄', +'跑著' => '跑着', +'跑著作' => '跑著作', +'跑著名' => '跑著名', +'跑著書' => '跑著書', +'跑著稱' => '跑著稱', +'跑著者' => '跑著者', +'跑著述' => '跑著述', +'跑著錄' => '跑著錄', +'跟著' => '跟着', +'跟著作' => '跟著作', +'跟著名' => '跟著名', +'跟著書' => '跟著書', +'跟著稱' => '跟著稱', +'跟著者' => '跟著者', +'跟著述' => '跟著述', +'跟著錄' => '跟著錄', +'跪著' => '跪着', +'跪著作' => '跪著作', +'跪著名' => '跪著名', +'跪著書' => '跪著書', +'跪著稱' => '跪著稱', +'跪著者' => '跪著者', +'跪著述' => '跪著述', +'跪著錄' => '跪著錄', +'跳著' => '跳着', +'跳著作' => '跳著作', +'跳著名' => '跳著名', +'跳著書' => '跳著書', +'跳著稱' => '跳著稱', +'跳著者' => '跳著者', +'跳著述' => '跳著述', +'跳著錄' => '跳著錄', +'踏著' => '踏着', +'踏著作' => '踏著作', +'踏著名' => '踏著名', +'踏著書' => '踏著書', +'踏著稱' => '踏著稱', +'踏著者' => '踏著者', +'踏著述' => '踏著述', +'踏著錄' => '踏著錄', +'踩著' => '踩着', +'踩著作' => '踩著作', +'踩著名' => '踩著名', +'踩著書' => '踩著書', +'踩著稱' => '踩著稱', +'踩著者' => '踩著者', +'踩著述' => '踩著述', +'踩著錄' => '踩著錄', +'躍著' => '躍着', +'躍著作' => '躍著作', +'躍著名' => '躍著名', +'躍著書' => '躍著書', +'躍著稱' => '躍著稱', +'躍著者' => '躍著者', +'躍著述' => '躍著述', +'躍著錄' => '躍著錄', +'身著' => '身着', +'身著作' => '身著作', +'身著名' => '身著名', +'身著書' => '身著書', +'身著稱' => '身著稱', +'身著者' => '身著者', +'身著述' => '身著述', +'身著錄' => '身著錄', +'躺著' => '躺着', +'躺著作' => '躺著作', +'躺著名' => '躺著名', +'躺著書' => '躺著書', +'躺著稱' => '躺著稱', +'躺著者' => '躺著者', +'躺著述' => '躺著述', +'躺著錄' => '躺著錄', +'軟體' => '軟件', +'載著' => '載着', +'載著作' => '載著作', +'載著名' => '載著名', +'載著書' => '載著書', +'載著稱' => '載著稱', +'載著者' => '載著者', +'載著述' => '載著述', +'載著錄' => '載著錄', +'轉著' => '轉着', +'轉著作' => '轉著作', +'轉著名' => '轉著名', +'轉著書' => '轉著書', +'轉著稱' => '轉著稱', +'轉著者' => '轉著者', +'轉著述' => '轉著述', +'轉著錄' => '轉著錄', +'辦著' => '辦着', +'辦著作' => '辦著作', +'辦著名' => '辦著名', +'辦著書' => '辦著書', +'辦著稱' => '辦著稱', +'辦著者' => '辦著者', +'辦著述' => '辦著述', +'辦著錄' => '辦著錄', +'追著' => '追着', +'追著作' => '追著作', +'追著名' => '追著名', +'追著書' => '追著書', +'追著稱' => '追著稱', +'追著者' => '追著者', +'追著述' => '追著述', +'追著錄' => '追著錄', +'逆著' => '逆着', +'逆著作' => '逆著作', +'逆著名' => '逆著名', +'逆著書' => '逆著書', +'逆著稱' => '逆著稱', +'逆著者' => '逆著者', +'逆著述' => '逆著述', +'逆著錄' => '逆著錄', +'連著' => '連着', +'連著作' => '連著作', +'連著名' => '連著名', +'連著書' => '連著書', +'連著稱' => '連著稱', +'連著者' => '連著者', +'連著述' => '連著述', +'連著錄' => '連著錄', +'逼著' => '逼着', +'逼著作' => '逼著作', +'逼著名' => '逼著名', +'逼著書' => '逼著書', +'逼著稱' => '逼著稱', +'逼著者' => '逼著者', +'逼著述' => '逼著述', +'逼著錄' => '逼著錄', +'遇著' => '遇着', +'遇著作' => '遇著作', +'遇著名' => '遇著名', +'遇著書' => '遇著書', +'遇著稱' => '遇著稱', +'遇著者' => '遇著者', +'遇著述' => '遇著述', +'遇著錄' => '遇著錄', +'達著' => '達着', +'達著作' => '達著作', +'達著名' => '達著名', +'達著書' => '達著書', +'達著稱' => '達著稱', +'達著者' => '達著者', +'達著述' => '達著述', +'達著錄' => '達著錄', +'遠著' => '遠着', +'遠著作' => '遠著作', +'遠著名' => '遠著名', +'遠著書' => '遠著書', +'遠著稱' => '遠著稱', +'遠著者' => '遠著者', +'遠著述' => '遠著述', +'遠著錄' => '遠著錄', +'配著' => '配着', +'配著作' => '配著作', +'配著名' => '配著名', +'配著書' => '配著書', +'配著稱' => '配著稱', +'配著者' => '配著者', +'配著述' => '配著述', +'配著錄' => '配著錄', +'醜著' => '醜着', +'醜著作' => '醜著作', +'醜著名' => '醜著名', +'醜著書' => '醜著書', +'醜著稱' => '醜著稱', +'醜著者' => '醜著者', +'醜著述' => '醜著述', +'醜著錄' => '醜著錄', +'釀著' => '釀着', +'釀著作' => '釀著作', +'釀著名' => '釀著名', +'釀著書' => '釀著書', +'釀著稱' => '釀著稱', +'釀著者' => '釀著者', +'釀著述' => '釀著述', +'釀著錄' => '釀著錄', +'鋪著' => '鋪着', +'鋪著作' => '鋪著作', +'鋪著名' => '鋪著名', +'鋪著書' => '鋪著書', +'鋪著稱' => '鋪著稱', +'鋪著者' => '鋪著者', +'鋪著述' => '鋪著述', +'鋪著錄' => '鋪著錄', +'閉著' => '閉着', +'閉著作' => '閉著作', +'閉著名' => '閉著名', +'閉著書' => '閉著書', +'閉著稱' => '閉著稱', +'閉著者' => '閉著者', +'閉著述' => '閉著述', +'閉著錄' => '閉著錄', +'開著' => '開着', +'開著作' => '開著作', +'開著名' => '開著名', +'開著書' => '開著書', +'開著稱' => '開著稱', +'開著者' => '開著者', +'開著述' => '開著述', +'開著錄' => '開著錄', +'閑著' => '閑着', +'閑著作' => '閑著作', +'閑著名' => '閑著名', +'閑著書' => '閑著書', +'閑著稱' => '閑著稱', +'閑著者' => '閑著者', +'閑著述' => '閑著述', +'閑著錄' => '閑著錄', +'關著' => '關着', +'關著作' => '關著作', +'關著名' => '關著名', +'關著書' => '關著書', +'關著稱' => '關著稱', +'關著者' => '關著者', +'關著述' => '關著述', +'關著錄' => '關著錄', +'亞塞拜然' => '阿塞拜疆', +'阿拉伯聯合大公國' => '阿拉伯聯合酋長國', +'附著' => '附着', +'附著作' => '附著作', +'附著名' => '附著名', +'附著書' => '附著書', +'附著稱' => '附著稱', +'附著者' => '附著者', +'附著述' => '附著述', +'附著錄' => '附著錄', +'陋著' => '陋着', +'陋著作' => '陋著作', +'陋著名' => '陋著名', +'陋著書' => '陋著書', +'陋著稱' => '陋著稱', +'陋著者' => '陋著者', +'陋著述' => '陋著述', +'陋著錄' => '陋著錄', +'陪著' => '陪着', +'陪著作' => '陪著作', +'陪著名' => '陪著名', +'陪著書' => '陪著書', +'陪著稱' => '陪著稱', +'陪著者' => '陪著者', +'陪著述' => '陪著述', +'陪著錄' => '陪著錄', +'隔著' => '隔着', +'隔著作' => '隔著作', +'隔著名' => '隔著名', +'隔著書' => '隔著書', +'隔著稱' => '隔著稱', +'隔著者' => '隔著者', +'隔著述' => '隔著述', +'隔著錄' => '隔著錄', +'隨著' => '隨着', +'隨著作' => '隨著作', +'隨著名' => '隨著名', +'隨著書' => '隨著書', +'隨著稱' => '隨著稱', +'隨著者' => '隨著者', +'隨著述' => '隨著述', +'隨著錄' => '隨著錄', +'雅著' => '雅着', +'雅著作' => '雅著作', +'雅著名' => '雅著名', +'雅著書' => '雅著書', +'雅著稱' => '雅著稱', +'雅著者' => '雅著者', +'雅著述' => '雅著述', +'雅著錄' => '雅著錄', +'雜著' => '雜着', +'雜著作' => '雜著作', +'雜著名' => '雜著名', +'雜著書' => '雜著書', +'雜著稱' => '雜著稱', +'雜著者' => '雜著者', +'雜著述' => '雜著述', +'雜著錄' => '雜著錄', +'冰淇淋' => '雪糕', +'響著' => '響着', +'響著作' => '響著作', +'響著名' => '響著名', +'響著書' => '響著書', +'響著稱' => '響著稱', +'響著者' => '響著者', +'響著述' => '響著述', +'響著錄' => '響著錄', +'頂著' => '頂着', +'頂著作' => '頂著作', +'頂著名' => '頂著名', +'頂著書' => '頂著書', +'頂著稱' => '頂著稱', +'頂著者' => '頂著者', +'頂著述' => '頂著述', +'頂著錄' => '頂著錄', +'順著' => '順着', +'順著作' => '順著作', +'順著名' => '順著名', +'順著書' => '順著書', +'順著稱' => '順著稱', +'順著者' => '順著者', +'順著述' => '順著述', +'順著錄' => '順著錄', +'頒布' => '頒佈', +'颁布' => '頒佈', +'領著' => '領着', +'領著作' => '領著作', +'領著名' => '領著名', +'領著書' => '領著書', +'領著稱' => '領著稱', +'領著者' => '領著者', +'領著述' => '領著述', +'領著錄' => '領著錄', +'飄著' => '飄着', +'飄著作' => '飄著作', +'飄著名' => '飄著名', +'飄著書' => '飄著書', +'飄著稱' => '飄著稱', +'飄著者' => '飄著者', +'飄著述' => '飄著述', +'飄著錄' => '飄著錄', +'馬爾地夫' => '馬爾代夫', +'馬利共和國' => '馬里共和國', +'土豆' => '馬鈴薯', +'駕著' => '駕着', +'駕著作' => '駕著作', +'駕著名' => '駕著名', +'駕著書' => '駕著書', +'駕著稱' => '駕著稱', +'駕著者' => '駕著者', +'駕著述' => '駕著述', +'駕著錄' => '駕著錄', +'騎著' => '騎着', +'騎著作' => '騎著作', +'騎著名' => '騎著名', +'騎著書' => '騎著書', +'騎著稱' => '騎著稱', +'騎著者' => '騎著者', +'騎著述' => '騎著述', +'騎著錄' => '騎著錄', +'騙著' => '騙着', +'騙著作' => '騙著作', +'騙著名' => '騙著名', +'騙著書' => '騙著書', +'騙著稱' => '騙著稱', +'騙著者' => '騙著者', +'騙著述' => '騙著述', +'騙著錄' => '騙著錄', +'高著' => '高着', +'高著作' => '高著作', +'高著名' => '高著名', +'高著書' => '高著書', +'高著稱' => '高著稱', +'高著者' => '高著者', +'高著述' => '高著述', +'高著錄' => '高著錄', +'髭著' => '髭着', +'髭著作' => '髭著作', +'髭著名' => '髭著名', +'髭著書' => '髭著書', +'髭著稱' => '髭著稱', +'髭著者' => '髭著者', +'髭著述' => '髭著述', +'髭著錄' => '髭著錄', +'鬥著' => '鬥着', +'鬥著作' => '鬥著作', +'鬥著名' => '鬥著名', +'鬥著書' => '鬥著書', +'鬥著稱' => '鬥著稱', +'鬥著者' => '鬥著者', +'鬥著述' => '鬥著述', +'鬥著錄' => '鬥著錄', +'麗著' => '麗着', +'麗著作' => '麗著作', +'麗著名' => '麗著名', +'麗著書' => '麗著書', +'麗著稱' => '麗著稱', +'麗著者' => '麗著者', +'麗著述' => '麗著述', +'麗著錄' => '麗著錄', +'黏著' => '黏着', +'黏著作' => '黏著作', +'黏著名' => '黏著名', +'黏著書' => '黏著書', +'黏著稱' => '黏著稱', +'黏著者' => '黏著者', +'黏著述' => '黏著述', +'黏著錄' => '黏著錄', +'點著' => '點着', +'點著作' => '點著作', +'點著名' => '點著名', +'點著書' => '點著書', +'點著稱' => '點著稱', +'點著者' => '點著者', +'點著述' => '點著述', +'點著錄' => '點著錄', ); $zh2CN = array( -"」" => "”", -"「" => "“", -"『" => "‘", -"』" => "’", -"記憶體" => "内存", -"預設" => "默认", -"串列" => "串行", -"串列加速器" => "串列加速器", -"乙太網" => "以太网", -"點陣圖" => "位图", -"常式" => "例程", -"游標" => "光标", -"光碟" => "光盘", -"光碟機" => "光驱", -"全形" => "全角", -"共用" => "共享", -"載入" => "加载", -"半形" => "半角", -"變數" => "变量", -"雜訊" => "噪声", -"因數" => "因子", -"功能變數名稱" => "域名", -"音效卡" => "声卡", -"字型大小" => "字号", -"字型檔" => "字库", -"欄位" => "字段", -"字元" => "字符", -"存檔" => "存盘", -"定址" => "寻址", -"章節附註" => "尾注", -"非同步" => "异步", -"匯流排" => "总线", -"括弧" => "括号", -"介面" => "接口", -"控制項" => "控件", -"許可權" => "权限", -"碟片" => "盘片", -"矽片" => "硅片", -"矽谷" => "硅谷", -"硬碟" => "硬盘", -"磁碟" => "磁盘", -"磁軌" => "磁道", -"程式控制" => "程控", -"運算元" => "算子", -"演算法" => "算法", -"晶片" => "芯片", -"晶元" => "芯片", -"片語" => "词组", -"軟碟機" => "软驱", -"快閃記憶體" => "快闪存储器", -"滑鼠" => "鼠标", -"進位" => "进制", -"互動式" => "交互式", -"優先順序" => "优先级", -"感測" => "传感", -"攜帶型" => "便携式", -"資訊理論" => "信息论", -"迴圈" => "循环", -"防寫" => "写保护", -"分散式" => "分布式", -"解析度" => "分辨率", -"伺服器" => "服务器", -"等於" => "等于", -"區域網" => "局域网", -"巨集" => "宏", -"掃瞄器" => "扫瞄仪", -"寬頻" => "宽带", -"資料庫" => "数据库", -"乳酪" => "奶酪", -"鉅賈" => "巨商", -"萬曆" => "万历", -"永曆" => "永历", -"辭彙" => "词汇", -"母音" => "元音", -"自由球" => "任意球", -"頭槌" => "头球", -"進球" => "入球", -"顆進球" => "粒入球", -"射門" => "打门", -"蓋火鍋" => "火锅盖帽", -"印表機" => "打印机", -"打印機" => "打印机", -"位元組" => "字节", -"字節" => "字节", -"列印" => "打印", -"打印" => "打印", -"硬體" => "硬件", -"二極體" => "二极管", -"二極管" => "二极管", -"三極體" => "三极管", -"三極管" => "三极管", -"數位" => "数码", -"數碼" => "数码", -"軟體" => "软件", -"軟件" => "软件", -"網路" => "网络", -"網絡" => "网络", -"人工智慧" => "人工智能", -"太空梭" => "航天飞机", -"穿梭機" => "航天飞机", -"網際網路" => "因特网", -"互聯網" => "因特网", -"機械人" => "机器人", -"機器人" => "机器人", -"行動電話" => "移动电话", -"流動電話" => "移动电话", -"調制解調器" => "调制解调器", -"數據機" => "调制解调器", -"短訊" => "短信", -"簡訊" => "短信", -"烏茲別克" => "乌兹别克斯坦", -"查德" => "乍得", -"葉門" => "也门", -"伯利茲" => "伯利兹", -"貝里斯" => "伯利兹", -"維德角" => "佛得角", -"克羅埃西亞" => "克罗地亚", -"甘比亞" => "冈比亚", -"幾內亞比索" => "几内亚比绍", -"列支敦斯登" => "列支敦士登", -"賴比瑞亞" => "利比里亚", -"迦納" => "加纳", -"加彭" => "加蓬", -"波札那" => "博茨瓦纳", -"卡達" => "卡塔尔", -"盧安達" => "卢旺达", -"瓜地馬拉" => "危地马拉", -"厄瓜多爾" => "厄瓜多尔", -"厄瓜多尔" => "厄瓜多尔", -"厄瓜多" => "厄瓜多尔", -"厄利垂亞" => "厄立特里亚", -"吉布地" => "吉布提", -"哈薩克" => "哈萨克斯坦", -"哥斯大黎加" => "哥斯达黎加", -"吐瓦魯" => "图瓦卢", -"土庫曼" => "土库曼斯坦", -"聖露西亞" => "圣卢西亚", -"聖吉斯納域斯" => "圣基茨和尼维斯", -"聖克里斯多福及尼維斯" => "圣基茨和尼维斯", -"聖文森及格瑞那丁" => "圣文森特和格林纳丁斯", -"聖馬利諾" => "圣马力诺", -"蓋亞那" => "圭亚那", -"坦尚尼亞" => "坦桑尼亚", -"衣索匹亞" => "埃塞俄比亚", -"衣索比亞" => "埃塞俄比亚", -"吉里巴斯" => "基里巴斯", -"塔吉克" => "塔吉克斯坦", -"塞拉利昂" => "塞拉利昂", -"塞普勒斯" => "塞浦路斯", -"塞席爾" => "塞舌尔", -"多米尼克" => "多米尼加国", -"安地卡及巴布達" => "安提瓜和巴布达", -"尼日利亞" => "尼日利亚", -"尼日利亚" => "尼日利亚", -"奈及利亞" => "尼日利亚", -"尼日爾" => "尼日尔", -"尼日尔" => "尼日尔", -"尼日" => "尼日尔", -"巴貝多" => "巴巴多斯", -"巴布亞紐幾內亞" => "巴布亚新几内亚", -"布基納法索" => "布基纳法索", -"布吉納法索" => "布基纳法索", -"蒲隆地" => "布隆迪", -"帛琉" => "帕劳", -"義大利" => "意大利", -"索羅門群島" => "所罗门群岛", -"汶萊" => "文莱", -"史瓦濟蘭" => "斯威士兰", -"斯洛維尼亞" => "斯洛文尼亚", -"紐西蘭" => "新西兰", -"格瑞那達" => "格林纳达", -"茅利塔尼亞" => "毛里塔尼亚", -"毛里裘斯" => "毛里求斯", -"模里西斯" => "毛里求斯", -"沙地阿拉伯" => "沙特阿拉伯", -"沙烏地阿拉伯" => "沙特阿拉伯", -"波士尼亞赫塞哥維納" => "波斯尼亚和黑塞哥维那", -"辛巴威" => "津巴布韦", -"宏都拉斯" => "洪都拉斯", -"千里達托貝哥" => "特立尼达和托巴哥", -"諾魯" => "瑙鲁", -"萬那杜" => "瓦努阿图", -"溫納圖" => "瓦努阿图", -"葛摩" => "科摩罗", -"象牙海岸" => "科特迪瓦", -"突尼西亞" => "突尼斯", -"索馬利亞" => "索马里", -"寮國" => "老挝", -"肯雅" => "肯尼亚", -"肯亞" => "肯尼亚", -"蘇利南" => "苏里南", -"莫三比克" => "莫桑比克", -"賴索托" => "莱索托", -"貝南" => "贝宁", -"尚比亞" => "赞比亚", -"亞塞拜然" => "阿塞拜疆", -"阿拉伯聯合大公國" => "阿拉伯联合酋长国", -"南韓" => "韩国", -"馬爾地夫" => "马尔代夫", -"馬爾他" => "马耳他", -"馬利共和國" => "马里共和国", -"即食麵" => "方便面", -"快速面" => "方便面", -"速食麵" => "方便面", -"泡麵" => "方便面", -"笨豬跳" => "蹦极跳", -"绑紧跳" => "蹦极跳", -"冷盤" => "凉菜", -"冷菜" => "凉菜", -"散钱" => "零钱", -"谐星" => "笑星", -"夜学" => "夜校", -"华乐" => "民乐", -"中樂" => "民乐", -"屋价" => "房价", -"計程車" => "出租车", -"公車" => "公共汽车", -"單車" => "自行车", -"節慶" => "节日", -"芝士" => "乾酪", -"狗隻" => "犬只", -"士多啤梨" => "草莓", -"忌廉" => "奶油", -"桌球" => "台球", -"撞球" => "台球", -"雪糕" => "冰淇淋", -"衞生" => "卫生", -"衛生" => "卫生", -"賓士" => "奔驰", -"平治" => "奔驰", -"平治之亂" => "平治之乱", -"平治之乱" => "平治之乱", -"積架" => "捷豹", -"福斯" => "大众", -"福士" => "大众", -"萬事得" => "马自达", -"寶獅" => "标志", -"拿破崙" => "拿破仑", -"布殊" => "布什", -"布希" => "布什", -"柯林頓" => "克林顿", -"海珊" => "萨达姆", -"梵谷" => "凡高", -"大衛碧咸" => "大卫·贝克汉姆", -"米高奧雲" => "迈克尔·欧文", -"卡佩雅蒂" => "珍妮弗·卡普里亚蒂", -"沙芬" => "马拉特·萨芬", -"舒麥加" => "迈克尔·舒马赫", -"希特拉" => "希特勒", -"黛安娜" => "戴安娜", +'16進位' => '16进位', +'16進位制' => '16进位制', +'『' => '‘', +'』' => '’', +'「' => '“', +'」' => '”', +'萬曆' => '万历', +'三極體' => '三极管', +'三極管' => '三极管', +'串列加速器' => '串列加速器', +'串列' => '串行', +'烏茲別克' => '乌兹别克斯坦', +'葉門' => '也门', +'芝士' => '乾酪', +'二極管' => '二极管', +'二極體' => '二极管', +'二進位制' => '二进位制', +'二進位' => '二进制', +'網際網路' => '互联网', +'互聯網' => '互联网', +'互動式' => '交互式', +'人工智慧' => '人工智能', +'甚麽' => '什么', +'甚麼' => '什么', +'乙太網' => '以太网', +'自由球' => '任意球', +'優先順序' => '优先级', +'感測' => '传感', +'伯利茲' => '伯利兹', +'貝里斯' => '伯利兹', +'點陣圖' => '位图', +'維德角' => '佛得角', +'常式' => '例程', +'侏儸紀' => '侏罗纪', +'攜帶型' => '便携式', +'資訊理論' => '信息论', +'母音' => '元音', +'游標' => '光标', +'光碟' => '光盘', +'光碟機' => '光驱', +'柯林頓' => '克林顿', +'克羅埃西亞' => '克罗地亚', +'進球' => '入球', +'全形' => '全角', +'八進位制' => '八进位制', +'八進位' => '八进制', +'公車' => '公共汽车', +'公車上書' => '公车上书', +'六進位制' => '六进位制', +'六進位' => '六进制', +'記憶體' => '内存', +'甘比亞' => '冈比亚', +'防寫' => '写保护', +'冷菜' => '凉菜', +'冷盤' => '凉菜', +'幾內亞比索' => '几内亚比绍', +'梵谷' => '凡高', +'計程車' => '出租车', +'分散式' => '分布式', +'解析度' => '分辨率', +'列支敦斯登' => '列支敦士登', +'賴比瑞亞' => '利比里亚', +'迦納' => '加纳', +'加彭' => '加蓬', +'載入' => '加载', +'十進位制' => '十进位制', +'十進位' => '十进制', +'半形' => '半角', +'波札那' => '博茨瓦纳', +'盧安達' => '卢旺达', +'衞生' => '卫生', +'衛生' => '卫生', +'瓜地馬拉' => '危地马拉', +'厄瓜多' => '厄瓜多尔', +'厄瓜多爾' => '厄瓜多尔', +'厄瓜多尔' => '厄瓜多尔', +'厄利垂亞' => '厄立特里亚', +'變數' => '变量', +'撞球' => '台球', +'桌球' => '台球', +'吉布地' => '吉布提', +'哈薩克' => '哈萨克斯坦', +'哥斯大黎加' => '哥斯达黎加', +'雜訊' => '噪声', +'因數' => '因子', +'吐瓦魯' => '图瓦卢', +'土庫曼' => '土库曼斯坦', +'聖露西亞' => '圣卢西亚', +'聖吉斯納域斯' => '圣基茨和尼维斯', +'聖克里斯多福及尼維斯' => '圣基茨和尼维斯', +'聖文森及格瑞那丁' => '圣文森特和格林纳丁斯', +'聖馬利諾' => '圣马力诺', +'蓋亞那' => '圭亚那', +'坦尚尼亞' => '坦桑尼亚', +'衣索比亞' => '埃塞俄比亚', +'衣索匹亞' => '埃塞俄比亚', +'功能變數名稱' => '域名', +'吉里巴斯' => '基里巴斯', +'塔吉克' => '塔吉克斯坦', +'塞拉利昂' => '塞拉利昂', +'塞普勒斯' => '塞浦路斯', +'塞席爾' => '塞舌尔', +'音效卡' => '声卡', +'多米尼克' => '多米尼加国', +'夜学' => '夜校', +'福士' => '大众', +'福斯' => '大众', +'大衛碧咸' => '大卫·贝克汉姆', +'頭槌' => '头球', +'賓士' => '奔驰', +'平治' => '奔驰', +'忌廉' => '奶油', +'乳酪' => '奶酪', +'字型大小' => '字号', +'字型檔' => '字库', +'欄位' => '字段', +'字元' => '字符', +'字節' => '字节', +'位元組' => '字节', +'存檔' => '存盘', +'安地卡及巴布達' => '安提瓜和巴布达', +'巨集' => '宏', +'寬頻' => '宽带', +'定址' => '寻址', +'奈及利亞' => '尼日利亚', +'尼日利亞' => '尼日利亚', +'尼日利亚' => '尼日利亚', +'尼日爾' => '尼日尔', +'尼日尔' => '尼日尔', +'章節附註' => '尾注', +'區域網' => '局域网', +'鉅賈' => '巨商', +'巴貝多' => '巴巴多斯', +'巴布亞紐幾內亞' => '巴布亚新几内亚', +'布希' => '布什', +'布殊' => '布什', +'布基納法索' => '布基纳法索', +'布吉納法索' => '布基纳法索', +'蒲隆地' => '布隆迪', +'希特拉' => '希特勒', +'帛琉' => '帕劳', +'平治之乱' => '平治之乱', +'平治之亂' => '平治之乱', +'非同步' => '异步', +'迴圈' => '循环', +'快閃記憶體' => '快闪存储器', +'匯流排' => '总线', +'義大利' => '意大利', +'黛安娜' => '戴安娜', +'屋价' => '房价', +'索羅門群島' => '所罗门群岛', +'打印' => '打印', +'列印' => '打印', +'印表機' => '打印机', +'打印機' => '打印机', +'射門' => '打门', +'掃瞄器' => '扫瞄仪', +'括弧' => '括号', +'拿破崙' => '拿破仑', +'積架' => '捷豹', +'介面' => '接口', +'控制項' => '控件', +'資料庫' => '数据库', +'汶萊' => '文莱', +'史瓦濟蘭' => '斯威士兰', +'斯洛維尼亞' => '斯洛文尼亚', +'紐西蘭' => '新西兰', +'即食麵' => '方便面', +'快速面' => '方便面', +'泡麵' => '方便面', +'速食麵' => '方便面', +'伺服器' => '服务器', +'機械人' => '机器人', +'機器人' => '机器人', +'許可權' => '权限', +'寶獅' => '标志', +'格瑞那達' => '格林纳达', +'榴槤' => '榴莲', +'榴梿' => '榴莲', +'茅利塔尼亞' => '毛里塔尼亚', +'毛里裘斯' => '毛里求斯', +'模里西斯' => '毛里求斯', +'华乐' => '民乐', +'中樂' => '民乐', +'永曆' => '永历', +'沙地阿拉伯' => '沙特阿拉伯', +'沙烏地阿拉伯' => '沙特阿拉伯', +'波士尼亞赫塞哥維納' => '波斯尼亚和黑塞哥维那', +'辛巴威' => '津巴布韦', +'宏都拉斯' => '洪都拉斯', +'滿16進位' => '满16进位', +'滿二進位' => '满二进位', +'滿八進位' => '满八进位', +'滿六進位' => '满六进位', +'滿十六進位' => '满十六进位', +'滿十進位' => '满十进位', +'蓋火鍋' => '火锅盖帽', +'千里達托貝哥' => '特立尼达和托巴哥', +'狗隻' => '犬只', +'卡佩雅蒂' => '珍妮弗·卡普里亚蒂', +'諾魯' => '瑙鲁', +'萬那杜' => '瓦努阿图', +'溫納圖' => '瓦努阿图', +'碟片' => '盘片', +'短訊' => '短信', +'簡訊' => '短信', +'矽尘' => '矽尘', +'矽塵' => '矽尘', +'矽肺' => '矽肺', +'矽钢' => '矽钢', +'矽鋼' => '矽钢', +'矽' => '硅', +'矽片' => '硅片', +'矽谷' => '硅谷', +'硬體' => '硬件', +'硬碟' => '硬盘', +'磁碟' => '磁盘', +'磁軌' => '磁道', +'葛摩' => '科摩罗', +'象牙海岸' => '科特迪瓦', +'行動電話' => '移动电话', +'流動電話' => '移动电话', +'程式控制' => '程控', +'突尼西亞' => '突尼斯', +'谐星' => '笑星', +'等於' => '等于', +'運算元' => '算子', +'演算法' => '算法', +'顆進球' => '粒入球', +'索馬利亞' => '索马里', +'網路' => '网络', +'網絡' => '网络', +'寮國' => '老挝', +'肯雅' => '肯尼亚', +'肯亞' => '肯尼亚', +'單車' => '自行车', +'太空梭' => '航天飞机', +'穿梭機' => '航天飞机', +'節慶' => '节日', +'晶元' => '芯片', +'晶片' => '芯片', +'蘇利南' => '苏里南', +'士多啤梨' => '草莓', +'莫三比克' => '莫桑比克', +'賴索托' => '莱索托', +'海珊' => '萨达姆', +'辭彙' => '词汇', +'片語' => '词组', +'調制解調器' => '调制解调器', +'數據機' => '调制解调器', +'貝南' => '贝宁', +'尚比亞' => '赞比亚', +'绑紧跳' => '蹦极跳', +'笨豬跳' => '蹦极跳', +'軟體' => '软件', +'軟件' => '软件', +'軟碟機' => '软驱', +'米高奧雲' => '迈克尔·欧文', +'舒麥加' => '迈克尔·舒马赫', +'遠程控制' => '远程控制', +'远程控制' => '远程控制', +'亞塞拜然' => '阿塞拜疆', +'阿拉伯聯合大公國' => '阿拉伯联合酋长国', +'散钱' => '零钱', +'南韓' => '韩国', +'馬爾地夫' => '马尔代夫', +'沙芬' => '马拉特·萨芬', +'馬爾他' => '马耳他', +'萬事得' => '马自达', +'馬利共和國' => '马里共和国', +'預設' => '默认', +'滑鼠' => '鼠标', ); $zh2SG = array( -"」" => "”", -"「" => "“", -"『" => "‘", -"』" => "’", -"方便面" => "快速面", -"速食麵" => "快速面", -"即食麵" => "快速面", -"泡麵" => "快速面", -"蹦极跳" => "绑紧跳", -"笨豬跳" => "绑紧跳", -"凉菜" => "冷菜", -"冷盤" => "冷菜", -"零钱" => "散钱", -"散紙" => "散钱", -"笑星" => "谐星", -"夜校" => "夜学", -"民乐" => "华乐", -"住房" => "住屋", -"房价" => "屋价", - +'『' => '‘', +'』' => '’', +'「' => '“', +'」' => '”', +'住房' => '住屋', +'凉菜' => '冷菜', +'冷盤' => '冷菜', +'民乐' => '华乐', +'夜校' => '夜学', +'房价' => '屋价', +'即食麵' => '快速面', +'速食麵' => '快速面', +'泡麵' => '快速面', +'方便面' => '快速面', +'零钱' => '散钱', +'散紙' => '散钱', +'榴莲' => '榴梿', +'榴蓮' => '榴梿', +'笨豬跳' => '绑紧跳', +'蹦极跳' => '绑紧跳', +'笑星' => '谐星', );
\ No newline at end of file diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 22144333..8cf8c096 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -24,15 +24,17 @@ */ /** - * This abstract class implements many basic API functions, and is the base of all API classes. + * This abstract class implements many basic API functions, and is the base of + * all API classes. * The class functions are divided into several areas of functionality: * - * Module parameters: Derived classes can define getAllowedParams() to specify which parameters to expect, - * how to parse and validate them. + * Module parameters: Derived classes can define getAllowedParams() to specify + * which parameters to expect,h ow to parse and validate them. * - * Profiling: various methods to allow keeping tabs on various tasks and their time costs + * Profiling: various methods to allow keeping tabs on various tasks and their + * time costs * - * Self-documentation: code to allow api to document its own state. + * Self-documentation: code to allow the API to document its own state * * @ingroup API */ @@ -56,8 +58,11 @@ abstract class ApiBase { private $mMainModule, $mModuleName, $mModulePrefix; /** - * Constructor - */ + * Constructor + * @param $mainModule ApiMain object + * @param $moduleName string Name of this module + * @param $modulePrefix string Prefix to use for parameter names + */ public function __construct($mainModule, $moduleName, $modulePrefix = '') { $this->mMainModule = $mainModule; $this->mModuleName = $moduleName; @@ -69,32 +74,34 @@ abstract class ApiBase { *****************************************************************************/ /** - * Evaluates the parameters, performs the requested query, and sets up the - * result. Concrete implementations of ApiBase must override this method to - * provide whatever functionality their module offers. Implementations must - * not produce any output on their own and are not expected to handle any - * errors. + * Evaluates the parameters, performs the requested query, and sets up + * the result. Concrete implementations of ApiBase must override this + * method to provide whatever functionality their module offers. + * Implementations must not produce any output on their own and are not + * expected to handle any errors. * - * The execute method will be invoked directly by ApiMain immediately before - * the result of the module is output. Aside from the constructor, implementations - * should assume that no other methods will be called externally on the module - * before the result is processed. + * The execute() method will be invoked directly by ApiMain immediately + * before the result of the module is output. Aside from the + * constructor, implementations should assume that no other methods + * will be called externally on the module before the result is + * processed. * - * The result data should be stored in the result object referred to by - * "getResult()". Refer to ApiResult.php for details on populating a result - * object. + * The result data should be stored in the ApiResult object available + * through getResult(). */ public abstract function execute(); /** - * Returns a String that identifies the version of the extending class. Typically - * includes the class name, the svn revision, timestamp, and last author. May - * be severely incorrect in many implementations! + * Returns a string that identifies the version of the extending class. + * Typically includes the class name, the svn revision, timestamp, and + * last author. Usually done with SVN's Id keyword + * @return string */ public abstract function getVersion(); /** * Get the name of the module being executed by this instance + * @return string */ public function getModuleName() { return $this->mModuleName; @@ -102,6 +109,7 @@ abstract class ApiBase { /** * Get parameter prefix (usually two letters or an empty string). + * @return string */ public function getModulePrefix() { return $this->mModulePrefix; @@ -109,6 +117,7 @@ abstract class ApiBase { /** * Get the name of the module as shown in the profiler log + * @return string */ public function getModuleProfileName($db = false) { if ($db) @@ -118,7 +127,8 @@ abstract class ApiBase { } /** - * Get main module + * Get the main module + * @return ApiMain object */ public function getMain() { return $this->mMainModule; @@ -127,14 +137,15 @@ abstract class ApiBase { /** * Returns true if this module is the main module ($this === $this->mMainModule), * false otherwise. + * @return bool */ public function isMain() { return $this === $this->mMainModule; } /** - * Get the result object. Please refer to the documentation in ApiResult.php - * for details on populating and accessing data in a result object. + * Get the result object + * @return ApiResult */ public function getResult() { // Main module has getResult() method overriden @@ -145,37 +156,45 @@ abstract class ApiBase { } /** - * Get the result data array + * Get the result data array (read-only) + * @return array */ - public function & getResultData() { + public function getResultData() { return $this->getResult()->getData(); } /** - * Set warning section for this module. Users should monitor this section to - * notice any changes in API. + * Set warning section for this module. Users should monitor this + * section to notice any changes in API. Multiple calls to this + * function will result in the warning messages being separated by + * newlines + * @param $warning string Warning message */ public function setWarning($warning) { - # If there is a warning already, append it to the existing one - $data =& $this->getResult()->getData(); + $data = $this->getResult()->getData(); if(isset($data['warnings'][$this->getModuleName()])) { # Don't add duplicate warnings $warn_regex = preg_quote($warning, '/'); if(preg_match("/{$warn_regex}(\\n|$)/", $data['warnings'][$this->getModuleName()]['*'])) return; - $warning = "{$data['warnings'][$this->getModuleName()]['*']}\n$warning"; - unset($data['warnings'][$this->getModuleName()]); + $oldwarning = $data['warnings'][$this->getModuleName()]['*']; + # If there is a warning already, append it to the existing one + $warning = "$oldwarning\n$warning"; + $this->getResult()->unsetValue('warnings', $this->getModuleName()); } $msg = array(); ApiResult :: setContent($msg, $warning); + $this->getResult()->disableSizeCheck(); $this->getResult()->addValue('warnings', $this->getModuleName(), $msg); + $this->getResult()->enableSizeCheck(); } /** * If the module may only be used with a certain format module, * it should override this method to return an instance of that formatter. * A value of null means the default format will be used. + * @return mixed instance of a derived class of ApiFormatBase, or null */ public function getCustomPrinter() { return null; @@ -183,6 +202,7 @@ abstract class ApiBase { /** * Generates help message for this module, or false if there is no description + * @return mixed string or false */ public function makeHelpMsg() { @@ -198,8 +218,15 @@ abstract class ApiBase { ); $msg = $lnPrfx . implode($lnPrfx, $msg) . "\n"; + if ($this->isReadMode()) + $msg .= "\nThis module requires read rights."; + if ($this->isWriteMode()) + $msg .= "\nThis module requires write rights."; if ($this->mustBePosted()) - $msg .= "\nThis module only accepts POST requests.\n"; + $msg .= "\nThis module only accepts POST requests."; + if ($this->isReadMode() || $this->isWriteMode() || + $this->mustBePosted()) + $msg .= "\n"; // Parameters $paramsMsg = $this->makeHelpMsgParameters(); @@ -220,16 +247,16 @@ abstract class ApiBase { if ($this->getMain()->getShowVersions()) { $versions = $this->getVersion(); - $pattern = '(\$.*) ([0-9a-z_]+\.php) (.*\$)'; + $pattern = '/(\$.*) ([0-9a-z_]+\.php) (.*\$)/i'; $replacement = '\\0' . "\n " . 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/api/\\2'; if (is_array($versions)) { foreach ($versions as &$v) - $v = eregi_replace($pattern, $replacement, $v); + $v = preg_replace($pattern, $replacement, $v); $versions = implode("\n ", $versions); } else - $versions = eregi_replace($pattern, $replacement, $versions); + $versions = preg_replace($pattern, $replacement, $versions); $msg .= "Version:\n $versions\n"; } @@ -241,6 +268,7 @@ abstract class ApiBase { /** * Generates the parameter descriptions for this module, to be displayed in the * module's help. + * @return string */ public function makeHelpMsgParameters() { $params = $this->getFinalParams(); @@ -311,6 +339,7 @@ abstract class ApiBase { /** * Returns the description string for this module + * @return mixed string or array of strings */ protected function getDescription() { return false; @@ -318,15 +347,18 @@ abstract class ApiBase { /** * Returns usage examples for this module. Return null if no examples are available. + * @return mixed string or array of strings */ protected function getExamples() { return false; } /** - * Returns an array of allowed parameters (keys) => default value for that parameter. - * Don't call this function directly: use getFinalParams() to allow hooks - * to modify parameters as needed. + * Returns an array of allowed parameters (parameter name) => (default + * value) or (parameter name) => (array with PARAM_* constants as keys) + * Don't call this function directly: use getFinalParams() to allow + * hooks to modify parameters as needed. + * @return array */ protected function getAllowedParams() { return false; @@ -334,24 +366,30 @@ abstract class ApiBase { /** * Returns an array of parameter descriptions. - * Don't call this functon directly: use getFinalParamDescription() to allow - * hooks to modify descriptions as needed. + * Don't call this functon directly: use getFinalParamDescription() to + * allow hooks to modify descriptions as needed. + * @return array */ protected function getParamDescription() { return false; } /** - * Get final list of parameters, after hooks have had - * a chance to tweak it as needed. + * Get final list of parameters, after hooks have had a chance to + * tweak it as needed. + * @return array */ public function getFinalParams() { $params = $this->getAllowedParams(); wfRunHooks('APIGetAllowedParams', array(&$this, &$params)); return $params; } - - + + /** + * Get final description, after hooks have had a chance to tweak it as + * needed. + * @return array + */ public function getFinalParamDescription() { $desc = $this->getParamDescription(); wfRunHooks('APIGetParamDescription', array(&$this, &$desc)); @@ -361,16 +399,21 @@ abstract class ApiBase { /** * This method mangles parameter name based on the prefix supplied to the constructor. * Override this method to change parameter name during runtime + * @param $paramName string Parameter name + * @return string Prefixed parameter name */ public function encodeParamName($paramName) { return $this->mModulePrefix . $paramName; } /** - * Using getAllowedParams(), makes an array of the values provided by the user, - * with key being the name of the variable, and value - validated value from user or default. - * limit=max will not be parsed if $parseMaxLimit is set to false; use this - * when the max limit is not definite, e.g. when getting revisions. + * Using getAllowedParams(), this function makes an array of the values + * provided by the user, with key being the name of the variable, and + * value - validated value from user or default. limit=max will not be + * parsed if $parseMaxLimit is set to false; use this when the max + * limit is not definitive yet, e.g. when getting revisions. + * @param $parseMaxLimit bool + * @return array */ public function extractRequestParams($parseMaxLimit = true) { $params = $this->getFinalParams(); @@ -384,6 +427,9 @@ abstract class ApiBase { /** * Get a value for the given parameter + * @param $paramName string Parameter name + * @param $parseMaxLimit bool see extractRequestParams() + * @return mixed Parameter value */ protected function getParameter($paramName, $parseMaxLimit = true) { $params = $this->getFinalParams(); @@ -393,6 +439,7 @@ abstract class ApiBase { /** * Die if none or more than one of a certain set of parameters is set + * @param $params array of parameter names */ public function requireOnlyOneParameter($params) { $required = func_get_args(); @@ -411,6 +458,7 @@ abstract class ApiBase { /** * Returns an array of the namespaces (by integer id) that exist on the * wiki. Used primarily in help documentation. + * @return array */ public static function getValidNamespaces() { static $mValidNamespaces = null; @@ -430,8 +478,10 @@ abstract class ApiBase { * Using the settings determine the value for the given parameter * * @param $paramName String: parameter name - * @param $paramSettings Mixed: default value or an array of settings using PARAM_* constants. + * @param $paramSettings Mixed: default value or an array of settings + * using PARAM_* constants. * @param $parseMaxLimit Boolean: parse limit when max is given? + * @return mixed Parameter value */ protected function getParameterFromSettings($paramName, $paramSettings, $parseMaxLimit) { @@ -550,14 +600,17 @@ abstract class ApiBase { * Return an array of values that were given in a 'a|b|c' notation, * after it optionally validates them against the list allowed values. * - * @param valueName - The name of the parameter (for error reporting) - * @param value - The value being parsed - * @param allowMultiple - Can $value contain more than one value separated by '|'? - * @param allowedValues - An array of values to check against. If null, all values are accepted. - * @return (allowMultiple ? an_array_of_values : a_single_value) + * @param $valueName string The name of the parameter (for error + * reporting) + * @param $value mixed The value being parsed + * @param $allowMultiple bool Can $value contain more than one value + * separated by '|'? + * @param $allowedValues mixed An array of values to check against. If + * null, all values are accepted. + * @return mixed (allowMultiple ? an_array_of_values : a_single_value) */ protected function parseMultiValue($valueName, $value, $allowMultiple, $allowedValues) { - if( trim($value) === "" ) + if( trim($value) === "" && $allowMultiple) return array(); $sizeLimit = $this->mMainModule->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1; $valuesList = explode('|', $value, $sizeLimit + 1); @@ -590,8 +643,14 @@ abstract class ApiBase { } /** - * Validate the value against the minimum and user/bot maximum limits. Prints usage info on failure. - */ + * Validate the value against the minimum and user/bot maximum limits. + * Prints usage info on failure. + * @param $paramName string Parameter name + * @param $value int Parameter value + * @param $min int Minimum value + * @param $max int Maximum value for users + * @param $botMax int Maximum value for sysops/bots + */ function validateLimit($paramName, $value, $min, $max, $botMax = null) { if (!is_null($min) && $value < $min) { $this->dieUsage($this->encodeParamName($paramName) . " may not be less than $min (set to $value)", $paramName); @@ -632,9 +691,13 @@ abstract class ApiBase { } /** - * Call main module's error handler + * Call the main module's error handler + * @param $description string Error text + * @param $errorCode string Error code + * @param $httpRespCode int HTTP response code */ public function dieUsage($description, $errorCode, $httpRespCode = 0) { + wfProfileClose(); throw new UsageException($description, $this->encodeParamName($errorCode), $httpRespCode); } @@ -698,11 +761,17 @@ abstract class ApiBase { 'noemail' => array('code' => 'noemail', 'info' => "The user has not specified a valid e-mail address, or has chosen not to receive e-mail from other users"), 'rcpatroldisabled' => array('code' => 'patroldisabled', 'info' => "Patrolling is disabled on this wiki"), 'markedaspatrollederror-noautopatrol' => array('code' => 'noautopatrol', 'info' => "You don't have permission to patrol your own changes"), + 'delete-toobig' => array('code' => 'bigdelete', 'info' => "You can't delete this page because it has more than \$1 revisions"), + 'movenotallowedfile' => array('code' => 'cantmovefile', 'info' => "You don't have permission to move files"), // API-specific messages + 'readrequired' => array('code' => 'readapidenied', 'info' => "You need read permission to use this module"), + 'writedisabled' => array('code' => 'noapiwrite', 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"), + 'writerequired' => array('code' => 'writeapidenied', 'info' => "You're not allowed to edit this wiki through the API"), 'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"), 'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"), 'nosuchpageid' => array('code' => 'nosuchpageid', 'info' => "There is no page with ID \$1"), + 'nosuchrevid' => array('code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1"), 'invaliduser' => array('code' => 'invaliduser', 'info' => "Invalid username ``\$1''"), 'invalidexpiry' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''"), 'pastexpiry' => array('code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past"), @@ -723,36 +792,47 @@ abstract class ApiBase { 'protect-invalidaction' => array('code' => 'protect-invalidaction', 'info' => "Invalid protection type ``\$1''"), 'protect-invalidlevel' => array('code' => 'protect-invalidlevel', 'info' => "Invalid protection level ``\$1''"), 'toofewexpiries' => array('code' => 'toofewexpiries', 'info' => "\$1 expiry timestamps were provided where \$2 were needed"), - + 'cantimport' => array('code' => 'cantimport', 'info' => "You don't have permission to import pages"), + 'cantimport-upload' => array('code' => 'cantimport-upload', 'info' => "You don't have permission to import uploaded pages"), + 'importnofile' => array('code' => 'nofile', 'info' => "You didn't upload a file"), + 'importuploaderrorsize' => array('code' => 'filetoobig', 'info' => 'The file you uploaded is bigger than the maximum upload size'), + 'importuploaderrorpartial' => array('code' => 'partialupload', 'info' => 'The file was only partially uploaded'), + 'importuploaderrortemp' => array('code' => 'notempdir', 'info' => 'The temporary upload directory is missing'), + 'importcantopen' => array('code' => 'cantopenfile', 'info' => "Couldn't open the uploaded file"), + 'import-noarticle' => array('code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified'), + 'importbadinterwiki' => array('code' => 'badinterwiki', 'info' => 'Invalid interwiki title specified'), + 'import-unknownerror' => array('code' => 'import-unknownerror', 'info' => "Unknown error on import: ``\$1''"), // ApiEditPage messages 'noimageredirect-anon' => array('code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects"), 'noimageredirect-logged' => array('code' => 'noimageredirect', 'info' => "You don't have permission to create image redirects"), 'spamdetected' => array('code' => 'spamdetected', 'info' => "Your edit was refused because it contained a spam fragment: ``\$1''"), 'filtered' => array('code' => 'filtered', 'info' => "The filter callback function refused your edit"), - 'contenttoobig' => array('code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 bytes"), + 'contenttoobig' => array('code' => 'contenttoobig', 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"), 'noedit-anon' => array('code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages"), 'noedit' => array('code' => 'noedit', 'info' => "You don't have permission to edit pages"), 'wasdeleted' => array('code' => 'pagedeleted', 'info' => "The page has been deleted since you fetched its timestamp"), 'blankpage' => array('code' => 'emptypage', 'info' => "Creating new, empty pages is not allowed"), 'editconflict' => array('code' => 'editconflict', 'info' => "Edit conflict detected"), 'hashcheckfailed' => array('code' => 'badmd5', 'info' => "The supplied MD5 hash was incorrect"), - 'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext and prependtext parameters must be set"), + 'missingtext' => array('code' => 'notext', 'info' => "One of the text, appendtext, prependtext and undo parameters must be set"), 'emptynewsection' => array('code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.'), + 'revwrongpage' => array('code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''"), + 'undo-failure' => array('code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits'), ); /** * Output the error message related to a certain array - * @param array $error Element of a getUserPermissionsErrors()-style array + * @param $error array Element of a getUserPermissionsErrors()-style array */ public function dieUsageMsg($error) { $parsed = $this->parseMsg($error); - $this->dieUsage($parsed['code'], $parsed['info']); + $this->dieUsage($parsed['info'], $parsed['code']); } /** * Return the error message related to a certain array - * @param array $error Element of a getUserPermissionsErrors()-style array + * @param $error array Element of a getUserPermissionsErrors()-style array * @return array('code' => code, 'info' => info) */ public function parseMsg($error) { @@ -769,27 +849,39 @@ abstract class ApiBase { /** * Internal code errors should be reported with this method + * @param $method string Method or function name + * @param $message string Error message */ protected static function dieDebug($method, $message) { wfDebugDieBacktrace("Internal error in $method: $message"); } /** - * Indicates if API needs to check maxlag + * Indicates if this module needs maxlag to be checked + * @return bool */ public function shouldCheckMaxlag() { return true; } /** - * Indicates if this module requires edit mode + * Indicates whether this module requires read rights + * @return bool + */ + public function isReadMode() { + return true; + } + /** + * Indicates whether this module requires write mode + * @return bool */ - public function isEditMode() { + public function isWriteMode() { return false; } /** * Indicates whether this module must be called with a POST request + * @return bool */ public function mustBePosted() { return false; @@ -839,6 +931,7 @@ abstract class ApiBase { /** * Total time the module was executed + * @return float */ public function getProfileTime() { if ($this->mTimeIn !== 0) @@ -882,6 +975,7 @@ abstract class ApiBase { /** * Total time the module used the database + * @return float */ public function getProfileDBTime() { if ($this->mDBTimeIn !== 0) @@ -889,8 +983,14 @@ abstract class ApiBase { return $this->mDBTime; } + /** + * Debugging function that prints a value and an optional backtrace + * @param $value mixed Value to print + * @param $name string Description of the printed value + * @param $backtrace bool If true, print a backtrace + */ public static function debugPrint($value, $name = 'unknown', $backtrace = false) { - print "\n\n<pre><b>Debuging value '$name':</b>\n\n"; + print "\n\n<pre><b>Debugging value '$name':</b>\n\n"; var_export($value); if ($backtrace) print "\n" . wfBacktrace(); @@ -899,9 +999,10 @@ abstract class ApiBase { /** - * Returns a String that identifies the version of this class. + * Returns a string that identifies the version of this class. + * @return string */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 47041 2009-02-09 14:39:41Z catrope $'; + return __CLASS__ . ': $Id: ApiBase.php 50217 2009-05-05 13:12:16Z tstarling $'; } } diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index dfb11061..1c0bd5ac 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -50,7 +50,6 @@ class ApiBlock extends ApiBase { */ public function execute() { global $wgUser, $wgBlockAllowsUTEdit; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); if($params['gettoken']) @@ -94,7 +93,7 @@ class ApiBlock extends ApiBase { $this->dieUsageMsg($retval); $res['user'] = $params['user']; - $res['userID'] = $userID; + $res['userID'] = intval($userID); $res['expiry'] = ($expiry == Block::infinity() ? 'infinite' : wfTimestamp(TS_ISO_8601, $expiry)); $res['reason'] = $params['reason']; if($params['anononly']) @@ -115,6 +114,10 @@ class ApiBlock extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'user' => null, @@ -163,6 +166,6 @@ class ApiBlock extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiBlock.php 43677 2008-11-18 15:21:04Z catrope $'; + return __CLASS__ . ': $Id: ApiBlock.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index c0212924..9431ad78 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -49,7 +49,6 @@ class ApiDelete extends ApiBase { */ public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); $this->requireOnlyOneParameter($params, 'title', 'pageid'); @@ -76,14 +75,18 @@ class ApiDelete extends ApiBase { $retval = self::deleteFile($params['token'], $titleObj, $params['oldimage'], $reason, false); if(count($retval)) // We don't care about multiple errors, just report one of them - $this->dieUsageMsg(current($retval)); + $this->dieUsageMsg(reset($retval)); } else { $articleObj = new Article($titleObj); + if($articleObj->isBigDeletion() && !$wgUser->isAllowed('bigdelete')) { + global $wgDeleteRevisionsLimit; + $this->dieUsageMsg(array('delete-toobig', $wgDeleteRevisionsLimit)); + } $retval = self::delete($articleObj, $params['token'], $reason); if(count($retval)) // We don't care about multiple errors, just report one of them - $this->dieUsageMsg(current($retval)); + $this->dieUsageMsg(reset($retval)); if($params['watch'] || $wgUser->getOption('watchdeletion')) $articleObj->doWatch(); @@ -133,9 +136,10 @@ class ApiDelete extends ApiBase { if($reason === false) return array(array('cannotdelete')); } - - if (!wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason))) - $this->dieUsageMsg(array('hookaborted')); + + $error = ''; + if (!wfRunHooks('ArticleDelete', array(&$article, &$wgUser, &$reason, $error))) + $this->dieUsageMsg(array('hookaborted', $error)); // Luckily, Article.php provides a reusable delete function that does the hard work for us if($article->doDeleteArticle($reason)) { @@ -173,6 +177,10 @@ class ApiDelete extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'title' => null, @@ -213,6 +221,6 @@ class ApiDelete extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiDelete.php 44541 2008-12-13 21:07:18Z mrzman $'; + return __CLASS__ . ': $Id: ApiDelete.php 48122 2009-03-07 12:58:41Z catrope $'; } } diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php index 40e38a0f..9e0bf56e 100644 --- a/includes/api/ApiDisabled.php +++ b/includes/api/ApiDisabled.php @@ -48,6 +48,10 @@ class ApiDisabled extends ApiBase { $this->dieUsage("The ``{$this->getModuleName()}'' module has been disabled.", 'moduledisabled'); } + public function isReadMode() { + return false; + } + public function getAllowedParams() { return array (); } @@ -67,6 +71,6 @@ class ApiDisabled extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiDisabled.php 41268 2008-09-25 20:50:50Z catrope $'; + return __CLASS__ . ': $Id: ApiDisabled.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index bc5dfa87..d4a57b83 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -43,12 +43,12 @@ class ApiEditPage extends ApiBase { public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); - $params = $this->extractRequestParams(); if(is_null($params['title'])) $this->dieUsageMsg(array('missingparam', 'title')); - if(is_null($params['text']) && is_null($params['appendtext']) && is_null($params['prependtext'])) + if(is_null($params['text']) && is_null($params['appendtext']) && + is_null($params['prependtext']) && + $params['undo'] == 0) $this->dieUsageMsg(array('missingtext')); if(is_null($params['token'])) $this->dieUsageMsg(array('missingparam', 'token')); @@ -58,6 +58,9 @@ class ApiEditPage extends ApiBase { $titleObj = Title::newFromText($params['title']); if(!$titleObj) $this->dieUsageMsg(array('invalidtitle', $params['title'])); + // Some functions depend on $wgTitle == $ep->mTitle + global $wgTitle; + $wgTitle = $titleObj; if($params['createonly'] && $titleObj->exists()) $this->dieUsageMsg(array('createonly-exists')); @@ -75,13 +78,50 @@ class ApiEditPage extends ApiBase { $toMD5 = $params['text']; if(!is_null($params['appendtext']) || !is_null($params['prependtext'])) { - $content = $articleObj->getContent(); + // For non-existent pages, Article::getContent() + // returns an interface message rather than '' + // We do want getContent()'s behavior for non-existent + // MediaWiki: pages, though + if($articleObj->getID() == 0 && $titleObj->getNamespace() != NS_MEDIAWIKI) + $content = ''; + else + $content = $articleObj->getContent(); $params['text'] = $params['prependtext'] . $content . $params['appendtext']; $toMD5 = $params['prependtext'] . $params['appendtext']; } + + if($params['undo'] > 0) + { + if($params['undoafter'] > 0) + { + if($params['undo'] < $params['undoafter']) + list($params['undo'], $params['undoafter']) = + array($params['undoafter'], $params['undo']); + $undoafterRev = Revision::newFromID($params['undoafter']); + } + $undoRev = Revision::newFromID($params['undo']); + if(is_null($undoRev) || $undoRev->isDeleted(Revision::DELETED_TEXT)) + $this->dieUsageMsg(array('nosuchrevid', $params['undo'])); + if($params['undoafter'] == 0) + $undoafterRev = $undoRev->getPrevious(); + if(is_null($undoafterRev) || $undoafterRev->isDeleted(Revision::DELETED_TEXT)) + $this->dieUsageMsg(array('nosuchrevid', $params['undoafter'])); + if($undoRev->getPage() != $articleObj->getID()) + $this->dieUsageMsg(array('revwrongpage', $undoRev->getID(), $titleObj->getPrefixedText())); + if($undoafterRev->getPage() != $articleObj->getID()) + $this->dieUsageMsg(array('revwrongpage', $undoafterRev->getID(), $titleObj->getPrefixedText())); + $newtext = $articleObj->getUndoText($undoRev, $undoafterRev); + if($newtext === false) + $this->dieUsageMsg(array('undo-failure')); + $params['text'] = $newtext; + // If no summary was given and we only undid one rev, + // use an autosummary + if(is_null($params['summary']) && $titleObj->getNextRevisionID($undoafterRev->getID()) == $params['undo']) + $params['summary'] = wfMsgForContent('undo-summary', $params['undo'], $undoRev->getUserText()); + } # See if the MD5 hash checks out - if(isset($params['md5'])) + if(!is_null($params['md5'])) if(md5($toMD5) !== $params['md5']) $this->dieUsageMsg(array('hashcheckfailed')); @@ -140,9 +180,9 @@ class ApiEditPage extends ApiBase { # Run hooks # Handle CAPTCHA parameters global $wgRequest; - if(isset($params['captchaid'])) + if(!is_null($params['captchaid'])) $wgRequest->setVal( 'wpCaptchaId', $params['captchaid'] ); - if(isset($params['captchaword'])) + if(!is_null($params['captchaword'])) $wgRequest->setVal( 'wpCaptchaWord', $params['captchaword'] ); $r = array(); if(!wfRunHooks('APIEditBeforeSave', array(&$ep, $ep->textbox1, &$r))) @@ -160,10 +200,6 @@ class ApiEditPage extends ApiBase { # Do the actual save $oldRevId = $articleObj->getRevIdFetched(); $result = null; - # *Something* is setting $wgTitle to a title corresponding to "Msg", - # but that breaks API mode detection through is_null($wgTitle) - global $wgTitle; - $wgTitle = null; # Fake $wgRequest for some hooks inside EditPage # FIXME: This interface SUCKS $oldRequest = $wgRequest; @@ -217,7 +253,7 @@ class ApiEditPage extends ApiBase { $r['new'] = ''; case EditPage::AS_SUCCESS_UPDATE: $r['result'] = "Success"; - $r['pageid'] = $titleObj->getArticleID(); + $r['pageid'] = intval($titleObj->getArticleID()); $r['title'] = $titleObj->getPrefixedText(); # HACK: We create a new Article object here because getRevIdFetched() # refuses to be run twice, and because Title::getLatestRevId() @@ -229,8 +265,8 @@ class ApiEditPage extends ApiBase { $r['nochange'] = ''; else { - $r['oldrevid'] = $oldRevId; - $r['newrevid'] = $newRevId; + $r['oldrevid'] = intval($oldRevId); + $r['newrevid'] = intval($newRevId); } break; default: @@ -243,6 +279,10 @@ class ApiEditPage extends ApiBase { return true; } + public function isWriteMode() { + return true; + } + protected function getDescription() { return 'Create and edit pages.'; } @@ -269,6 +309,12 @@ class ApiEditPage extends ApiBase { 'md5' => null, 'prependtext' => null, 'appendtext' => null, + 'undo' => array( + ApiBase :: PARAM_TYPE => 'integer' + ), + 'undoafter' => array( + ApiBase :: PARAM_TYPE => 'integer' + ), ); } @@ -300,17 +346,23 @@ class ApiEditPage extends ApiBase { 'prependtext' => array( 'Add this text to the beginning of the page. Overrides text.', 'Don\'t use together with section: that won\'t do what you expect.'), 'appendtext' => 'Add this text to the end of the page. Overrides text', + 'undo' => 'Undo this revision. Overrides text, prependtext and appendtext', + 'undoafter' => 'Undo all revisions from undo to this one. If not set, just undo one revision', ); } protected function getExamples() { return array ( "Edit a page (anonymous user):", - " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\" + " api.php?action=edit&title=Test&summary=test%20summary&text=article%20content&basetimestamp=20070824123454&token=%2B\\", + "Prepend __NOTOC__ to a page (anonymous user):", + " api.php?action=edit&title=Test&summary=NOTOC&minor&prependtext=__NOTOC__%0A&basetimestamp=20070824123454&token=%2B\\", + "Undo r13579 through r13585 with autosummary(anonymous user):", + " api.php?action=edit&title=Test&undo=13585&undoafter=13579&basetimestamp=20070824123454&token=%2B\\", ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiEditPage.php 44394 2008-12-10 14:12:54Z catrope $'; + return __CLASS__ . ': $Id: ApiEditPage.php 50220 2009-05-05 14:07:59Z tstarling $'; } } diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php index fbdf495f..9bb504fb 100644 --- a/includes/api/ApiEmailUser.php +++ b/includes/api/ApiEmailUser.php @@ -39,14 +39,11 @@ class ApiEmailUser extends ApiBase { public function execute() { global $wgUser; - // Check whether email is enabled if ( !EmailUserForm::userEmailEnabled() ) $this->dieUsageMsg( array( 'usermaildisabled' ) ); - - $this->getMain()->requestWriteMode(); + $params = $this->extractRequestParams(); - // Check required parameters if ( !isset( $params['target'] ) ) $this->dieUsageMsg( array( 'missingparam', 'target' ) ); @@ -79,6 +76,10 @@ class ApiEmailUser extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'target' => null, @@ -112,7 +113,7 @@ class ApiEmailUser extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiEmailUser.php 41269 2008-09-25 21:39:36Z catrope $'; + return __CLASS__ . ': $Id: ApiEmailUser.php 48091 2009-03-06 13:49:44Z catrope $'; } }
\ No newline at end of file diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index 109b6552..0859232e 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -89,7 +89,7 @@ class ApiFeedWatchlist extends ApiBase { $data = $module->getResultData(); $feedItems = array (); - foreach ($data['query']['watchlist'] as $info) { + foreach ((array)$data['query']['watchlist'] as $info) { $feedItems[] = $this->createFeedItem($info); } @@ -175,6 +175,6 @@ class ApiFeedWatchlist extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFeedWatchlist.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 46848 2009-02-05 15:31:06Z catrope $'; } } diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 9efbbbe0..cc7434c6 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -38,9 +38,11 @@ abstract class ApiFormatBase extends ApiBase { private $mIsHtml, $mFormat, $mUnescapeAmps, $mHelp, $mCleared; /** - * Create a new instance of the formatter. - * If the format name ends with 'fm', wrap its output in the proper HTML. - */ + * Constructor + * If $format ends with 'fm', pretty-print the output in HTML. + * @param $main ApiMain + * @param $format string Format name + */ public function __construct($main, $format) { parent :: __construct($main, $format); @@ -61,9 +63,8 @@ abstract class ApiFormatBase extends ApiBase { public abstract function getMimeType(); /** - * If formatter outputs data results as is, the results must first be sanitized. - * An XML formatter on the other hand uses special tags, such as "_element" for special handling, - * and thus needs to override this function to return true. + * Whether this formatter needs raw data such as _element tags + * @return bool */ public function getNeedsRawData() { return false; @@ -71,36 +72,40 @@ abstract class ApiFormatBase extends ApiBase { /** * Get the internal format name + * @return string */ public function getFormat() { return $this->mFormat; } /** - * Specify whether or not ampersands should be escaped to '&' when rendering. This - * should only be set to true for the help message when rendered in the default (xmlfm) - * format. This is a temporary special-case fix that should be removed once the help - * has been reworked to use a fully html interface. + * Specify whether or not sequences like &quot; should be unescaped + * to " . This should only be set to true for the help message + * when rendered in the default (xmlfm) format. This is a temporary + * special-case fix that should be removed once the help has been + * reworked to use a fully HTML interface. * - * @param boolean Whether or not ampersands should be escaped. + * @param $b bool Whether or not ampersands should be escaped. */ public function setUnescapeAmps ( $b ) { $this->mUnescapeAmps = $b; } /** - * Returns true when an HTML filtering printer should be used. + * Returns true when the HTML pretty-printer should be used. * The default implementation assumes that formats ending with 'fm' * should be formatted in HTML. + * @return bool */ public function getIsHtml() { return $this->mIsHtml; } /** - * Initialize the printer function and prepares the output headers, etc. + * Initialize the printer function and prepare the output headers, etc. * This method must be the first outputing method during execution. * A help screen's header is printed for the HTML-based output + * @param $isError bool Whether an error message is printed */ function initPrinter($isError) { $isHtml = $this->getIsHtml(); @@ -167,8 +172,10 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or } /** - * The main format printing function. Call it to output the result string to the user. - * This function will automatically output HTML when format name ends in 'fm'. + * The main format printing function. Call it to output the result + * string to the user. This function will automatically output HTML + * when format name ends in 'fm'. + * @param $text string */ public function printText($text) { if ($this->getIsHtml()) @@ -188,15 +195,18 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or } /** - * Says pretty-printer that it should use *bold* and $italics$ formatting - */ + * Sets whether the pretty-printer should format *bold* and $italics$ + * @param $help bool + */ public function setHelp( $help = true ) { $this->mHelp = true; } /** - * Prety-print various elements in HTML format, such as xml tags and URLs. - * This method also replaces any '<' with < + * Prety-print various elements in HTML format, such as xml tags and + * URLs. This method also escapes characters like < + * @param $text string + * @return string */ protected function formatHTML($text) { global $wgUrlProtocols; @@ -209,14 +219,14 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or // identify URLs $protos = implode("|", $wgUrlProtocols); # This regex hacks around bug 13218 (" included in the URL) - $text = preg_replace("#(($protos).*?)(")?([ \\'\"()<\n])#", '<a href="\\1">\\1</a>\\3\\4', $text); + $text = preg_replace("#(($protos).*?)(")?([ \\'\"<>\n]|<|>|")#", '<a href="\\1">\\1</a>\\3\\4', $text); // identify requests to api.php $text = preg_replace("#api\\.php\\?[^ \\()<\n\t]+#", '<a href="\\0">\\0</a>', $text); if( $this->mHelp ) { // make strings inside * bold - $text = ereg_replace("\\*[^<>\n]+\\*", '<b>\\0</b>', $text); + $text = preg_replace("#\\*[^<>\n]+\\*#", '<b>\\0</b>', $text); // make strings inside $ italic - $text = ereg_replace("\\$[^<>\n]+\\$", '<b><i>\\0</i></b>', $text); + $text = preg_replace("#\\$[^<>\n]+\\$#", '<b><i>\\0</i></b>', $text); } /* Temporary fix for bad links in help messages. As a special case, @@ -229,9 +239,6 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or return $text; } - /** - * Returns usage examples for this format. - */ protected function getExamples() { return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName(); } @@ -241,7 +248,7 @@ See <a href='http://www.mediawiki.org/wiki/API'>complete documentation</a>, or } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $'; } } @@ -256,14 +263,21 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } /** - * Call this method to initialize output data. See self::execute() + * Call this method to initialize output data. See execute() + * @param $result ApiResult + * @param $feed object an instance of one of the $wgFeedClasses classes + * @param $feedItems array of FeedItem objects */ public static function setResult($result, $feed, $feedItems) { // Store output in the Result data. // This way we can check during execution if any error has occured - $data = & $result->getData(); - $data['_feed'] = $feed; - $data['_feeditems'] = $feedItems; + // Disable size checking for this because we can't continue + // cleanly; size checking would cause more problems than it'd + // solve + $result->disableSizeCheck(); + $result->addValue(null, '_feed', $feed); + $result->addValue(null, '_feeditems', $feedItems); + $result->enableSizeCheck(); } /** @@ -282,8 +296,8 @@ class ApiFormatFeedWrapper extends ApiFormatBase { /** * This class expects the result data to be in a custom format set by self::setResult() - * $result['_feed'] - an instance of one of the $wgFeedClasses classes - * $result['_feeditems'] - an array of FeedItem instances + * $result['_feed'] - an instance of one of the $wgFeedClasses classes + * $result['_feeditems'] - an array of FeedItem instances */ public function execute() { $data = $this->getResultData(); @@ -302,6 +316,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 43470 2008-11-14 00:30:34Z tstarling $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 48521 2009-03-18 19:25:29Z ialex $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index 1d89eb18..7b5a02a4 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -61,7 +61,7 @@ class ApiFormatJson extends ApiFormatBase { // Some versions of PHP have a broken json_encode, see PHP bug // 46944. Test encoding an affected character (U+20000) to // avoid this. - if (!function_exists('json_encode') || $this->getIsHtml() || strtolower(json_encode("\xf0\xa0\x80\x80")) != '\ud840\udc00') { + if (!function_exists('json_encode') || $this->getIsHtml() || strtolower(json_encode("\xf0\xa0\x80\x80")) != '"\ud840\udc00"') { $json = new Services_JSON(); $this->printText($prefix . $json->encode($this->getResultData(), $this->getIsHtml()) . $suffix); } else { @@ -89,6 +89,6 @@ class ApiFormatJson extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatJson.php 45682 2009-01-12 19:06:33Z raymond $'; + return __CLASS__ . ': $Id: ApiFormatJson.php 48713 2009-03-23 19:58:07Z catrope $'; } } diff --git a/includes/api/ApiFormatJson_json.php b/includes/api/ApiFormatJson_json.php index 4b29ff56..8cb3606d 100644 --- a/includes/api/ApiFormatJson_json.php +++ b/includes/api/ApiFormatJson_json.php @@ -45,14 +45,14 @@ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * -* @ingroup API -* @author Michal Migurski <mike-json@teczno.com> -* @author Matt Knapp <mdknapp[at]gmail[dot]com> -* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> -* @copyright 2005 Michal Migurski -* @version CVS: $Id: ApiFormatJson_json.php 45682 2009-01-12 19:06:33Z raymond $ -* @license http://www.opensource.org/licenses/bsd-license.php -* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198 +* @ingroup API +* @author Michal Migurski <mike-json@teczno.com> +* @author Matt Knapp <mdknapp[at]gmail[dot]com> +* @author Brett Stimmerman <brettstimmerman[at]gmail[dot]com> +* @copyright 2005 Michal Migurski +* @version CVS: $Id: ApiFormatJson_json.php 45765 2009-01-15 10:18:44Z catrope $ +* @license http://www.opensource.org/licenses/bsd-license.php +* @see http://pear.php.net/pepr/pepr-proposal-show.php?id=198 */ /** @@ -115,715 +115,715 @@ define('SERVICES_JSON_SUPPRESS_ERRORS', 32); */ class Services_JSON { - /** - * constructs a new JSON instance - * - * @param int $use object behavior flags; combine with boolean-OR - * - * possible values: - * - SERVICES_JSON_LOOSE_TYPE: loose typing. - * "{...}" syntax creates associative arrays - * instead of objects in decode(). - * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. - * Values which can't be encoded (e.g. resources) - * appear as NULL instead of throwing errors. - * By default, a deeply-nested resource will - * bubble up with an error, so all return values - * from encode() should be checked with isError() - */ - function Services_JSON($use = 0) - { - $this->use = $use; - } - - /** - * convert a string from one UTF-16 char to one UTF-8 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf16 UTF-16 character - * @return string UTF-8 character - * @access private - */ - function utf162utf8($utf16) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - } - - $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); - - switch(true) { - case ((0x7F & $bytes) == $bytes): - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x7F & $bytes); - - case (0x07FF & $bytes) == $bytes: - // return a 2-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xC0 | (($bytes >> 6) & 0x1F)) - . chr(0x80 | ($bytes & 0x3F)); - - case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC: - // return a 4-byte UTF-8 character - $char = ((($bytes & 0x03FF) << 10) - | ((ord($utf16{2}) & 0x03) << 8) - | ord($utf16{3})); - $char += 0x10000; - return chr(0xF0 | (($char >> 18) & 0x07)) - . chr(0x80 | (($char >> 12) & 0x3F)) - . chr(0x80 | (($char >> 6) & 0x3F)) - . chr(0x80 | ($char & 0x3F)); - - case (0xFFFF & $bytes) == $bytes: - // return a 3-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xE0 | (($bytes >> 12) & 0x0F)) - . chr(0x80 | (($bytes >> 6) & 0x3F)) - . chr(0x80 | ($bytes & 0x3F)); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * convert a string from one UTF-8 char to one UTF-16 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf8 UTF-8 character - * @return string UTF-16 character - * @access private - */ - function utf82utf16($utf8) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); - } - - switch(strlen($utf8)) { - case 1: - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return $utf8; - - case 2: - // return a UTF-16 character from a 2-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x07 & (ord($utf8{0}) >> 2)) - . chr((0xC0 & (ord($utf8{0}) << 6)) - | (0x3F & ord($utf8{1}))); - - case 3: - // return a UTF-16 character from a 3-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr((0xF0 & (ord($utf8{0}) << 4)) - | (0x0F & (ord($utf8{1}) >> 2))) - . chr((0xC0 & (ord($utf8{1}) << 6)) - | (0x7F & ord($utf8{2}))); - - case 4: - // return a UTF-16 surrogate pair from a 4-byte UTF-8 char - if(ord($utf8{0}) > 0xF4) return ''; # invalid - $char = ((0x1C0000 & (ord($utf8{0}) << 18)) - | (0x03F000 & (ord($utf8{1}) << 12)) - | (0x000FC0 & (ord($utf8{2}) << 6)) - | (0x00003F & ord($utf8{3}))); - if($char > 0x10FFFF) return ''; # invalid - $char -= 0x10000; - return chr(0xD8 | (($char >> 18) & 0x03)) - . chr(($char >> 10) & 0xFF) - . chr(0xDC | (($char >> 8) & 0x03)) - . chr($char & 0xFF); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * @param bool $pretty pretty-print output with indents and newlines - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encode($var, $pretty=false) - { - $this->indent = 0; - $this->pretty = $pretty; - $this->nameValSeparator = $pretty ? ': ' : ':'; - return $this->encode2($var); - } - - /** - * encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access private - */ - function encode2($var) - { - if ($this->pretty) { - $close = "\n" . str_repeat("\t", $this->indent); - $open = $close . "\t"; - $mid = ',' . $open; - } - else { - $open = $close = ''; - $mid = ','; - } - - switch (gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; - - case 'NULL': - return 'null'; - - case 'integer': - return (int) $var; - - case 'double': - case 'float': - return (float) $var; - - case 'string': - // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - /* - * Iterate over every character in the string, - * escaping with a slash or encoding to UTF-8 where necessary - */ - for ($c = 0; $c < $strlen_var; ++$c) { - - $ord_var_c = ord($var{$c}); - - switch (true) { - case $ord_var_c == 0x08: - $ascii .= '\b'; - break; - case $ord_var_c == 0x09: - $ascii .= '\t'; - break; - case $ord_var_c == 0x0A: - $ascii .= '\n'; - break; - case $ord_var_c == 0x0C: - $ascii .= '\f'; - break; - case $ord_var_c == 0x0D: - $ascii .= '\r'; - break; - - case $ord_var_c == 0x22: - case $ord_var_c == 0x2F: - case $ord_var_c == 0x5C: - // double quote, slash, slosh - $ascii .= '\\'.$var{$c}; - break; - - case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): - // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; - break; - - case (($ord_var_c & 0xE0) == 0xC0): - // characters U-00000080 - U-000007FF, mask 110XXXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c + 1})); - $c += 1; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF0) == 0xE0): - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2})); - $c += 2; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF8) == 0xF0): - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - // These will always return a surrogate pair - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3})); - $c += 3; - $utf16 = $this->utf82utf16($char); - if($utf16 == '') { - $ascii .= '\ufffd'; - } else { - $utf16 = str_split($utf16, 2); - $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1])); - } - break; - } - } - - return '"'.$ascii.'"'; - - case 'array': - /* - * As per JSON spec if any array key is not an integer - * we must treat the the whole array as an object. We - * also try to catch a sparsely populated associative - * array with numeric keys here because some JS engines - * will create an array with empty indexes up to - * max_index which can cause memory issues and because - * the keys, which may be relevant, will be remapped - * otherwise. - * - * As per the ECMA and JSON specification an object may - * have any string as a property. Unfortunately due to - * a hole in the ECMA specification if the key is a - * ECMA reserved word or starts with a digit the - * parameter is only accessible using ECMAScript's - * bracket notation. - */ - - // treat as a JSON object - if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { - $this->indent++; - $properties = array_map(array($this, 'name_value'), - array_keys($var), - array_values($var)); - $this->indent--; - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . $open . join($mid, $properties) . $close . '}'; - } - - // treat it like a regular array - $this->indent++; - $elements = array_map(array($this, 'encode2'), $var); - $this->indent--; - - foreach($elements as $element) { - if(Services_JSON::isError($element)) { - return $element; - } - } - - return '[' . $open . join($mid, $elements) . $close . ']'; - - case 'object': - $vars = get_object_vars($var); - - $this->indent++; - $properties = array_map(array($this, 'name_value'), - array_keys($vars), - array_values($vars)); - $this->indent--; - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . $open . join($mid, $properties) . $close . '}'; - - default: - return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) - ? 'null' - : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); - } - } - - /** - * array-walking function for use in generating JSON-formatted name-value pairs - * - * @param string $name name of key to use - * @param mixed $value reference to an array element to be encoded - * - * @return string JSON-formatted name-value pair, like '"name":value' - * @access private - */ - function name_value($name, $value) - { - $encoded_value = $this->encode2($value); - - if(Services_JSON::isError($encoded_value)) { - return $encoded_value; - } - - return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value; - } - - /** - * reduce a string by removing leading and trailing comments and whitespace - * - * @param $str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - * @access private - */ - function reduce_string($str) - { - $str = preg_replace(array( - - // eliminate single line comments in '// ...' form - '#^\s*//(.+)$#m', - - // eliminate multi-line comments in '/* ... */' form, at start of string - '#^\s*/\*(.+)\*/#Us', - - // eliminate multi-line comments in '/* ... */' form, at end of string - '#/\*(.+)\*/\s*$#Us' - - ), '', $str); - - // eliminate extraneous space - return trim($str); - } - - /** - * decodes a JSON string into appropriate variable - * - * @param string $str JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given JSON input string. - * See argument 1 to Services_JSON() above for object-output behavior. - * Note that decode() always returns strings - * in ASCII or UTF-8 format! - * @access public - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch (strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - $m = array(); - - if (is_numeric($str)) { - // Lookie-loo, it's a number - - // This would work on its own, but I'm trying to be - // good about returning integers where appropriate: - // return (float)$str; - - // Return float or int, as appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { - // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c < $strlen_chrs; ++$c) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); - - switch (true) { - case $substr_chrs_c_2 == '\b': - $utf8 .= chr(0x08); - ++$c; - break; - case $substr_chrs_c_2 == '\t': - $utf8 .= chr(0x09); - ++$c; - break; - case $substr_chrs_c_2 == '\n': - $utf8 .= chr(0x0A); - ++$c; - break; - case $substr_chrs_c_2 == '\f': - $utf8 .= chr(0x0C); - ++$c; - break; - case $substr_chrs_c_2 == '\r': - $utf8 .= chr(0x0D); - ++$c; - break; - - case $substr_chrs_c_2 == '\\"': - case $substr_chrs_c_2 == '\\\'': - case $substr_chrs_c_2 == '\\\\': - case $substr_chrs_c_2 == '\\/': - if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || - ($delim == "'" && $substr_chrs_c_2 != '\\"')) { - $utf8 .= $chrs{++$c}; - } - break; - - case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)): - // escaped unicode surrogate pair - $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) - . chr(hexdec(substr($chrs, ($c + 4), 2))) - . chr(hexdec(substr($chrs, ($c + 8), 2))) - . chr(hexdec(substr($chrs, ($c + 10), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 11; - break; - - case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): - // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) - . chr(hexdec(substr($chrs, ($c + 4), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 5; - break; - - case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): - $utf8 .= $chrs{$c}; - break; - - case ($ord_chrs_c & 0xE0) == 0xC0: - // characters U-00000080 - U-000007FF, mask 110XXXXX - //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); - ++$c; - break; - - case ($ord_chrs_c & 0xF0) == 0xE0: - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); - $c += 2; - break; - - case ($ord_chrs_c & 0xF8) == 0xF0: - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); - $c += 3; - break; - - case ($ord_chrs_c & 0xFC) == 0xF8: - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); - $c += 4; - break; - - case ($ord_chrs_c & 0xFE) == 0xFC: - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); - $c += 5; - break; - - } - - } - - return $utf8; - - } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { - // array, or object notation - - if ($str{0} == '[') { - $stk = array(SERVICES_JSON_IN_ARR); - $arr = array(); - } else { - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = new stdClass(); - } - } - - array_push($stk, array('what' => SERVICES_JSON_SLICE, - 'where' => 0, - 'delim' => false)); - - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if ($chrs == '') { - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } else { - return $obj; - - } - } - - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c <= $strlen_chrs; ++$c) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { - // found a comma that is not inside a string, array, etc., - // OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - // we are in an object, so figure - // out the property name and set an - // element in an associative array, - // for now - $parts = array(); - - if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } - - } - - } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { - // found a quote, and we are not inside a string - array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif (($chrs{$c} == $top['delim']) && - ($top['what'] == SERVICES_JSON_IN_STR) && - (($chrs{$c - 1} != '\\') || - ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { - // found a quote, we're in a string, and it's not escaped - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '[') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { - // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '{') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { - // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($substr_chrs_c_2 == '/*') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { - // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for ($i = $top['where']; $i <= $c; ++$i) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } - - } - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - return $obj; - - } - - } - } - } - - /** - * @todo Ultimately, this should just call PEAR::isError() - */ - function isError($data, $code = null) - { - if (class_exists('pear')) { - return PEAR::isError($data, $code); - } elseif (is_object($data) && (get_class($data) == 'services_json_error' || - is_subclass_of($data, 'services_json_error'))) { - return true; - } - - return false; - } + /** + * constructs a new JSON instance + * + * @param int $use object behavior flags; combine with boolean-OR + * + * possible values: + * - SERVICES_JSON_LOOSE_TYPE: loose typing. + * "{...}" syntax creates associative arrays + * instead of objects in decode(). + * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. + * Values which can't be encoded (e.g. resources) + * appear as NULL instead of throwing errors. + * By default, a deeply-nested resource will + * bubble up with an error, so all return values + * from encode() should be checked with isError() + */ + function Services_JSON($use = 0) + { + $this->use = $use; + } + + /** + * convert a string from one UTF-16 char to one UTF-8 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf16 UTF-16 character + * @return string UTF-8 character + * @access private + */ + function utf162utf8($utf16) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); + } + + $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); + + switch(true) { + case ((0x7F & $bytes) == $bytes): + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x7F & $bytes); + + case (0x07FF & $bytes) == $bytes: + // return a 2-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xC0 | (($bytes >> 6) & 0x1F)) + . chr(0x80 | ($bytes & 0x3F)); + + case (0xFC00 & $bytes) == 0xD800 && strlen($utf16) >= 4 && (0xFC & ord($utf16{2})) == 0xDC: + // return a 4-byte UTF-8 character + $char = ((($bytes & 0x03FF) << 10) + | ((ord($utf16{2}) & 0x03) << 8) + | ord($utf16{3})); + $char += 0x10000; + return chr(0xF0 | (($char >> 18) & 0x07)) + . chr(0x80 | (($char >> 12) & 0x3F)) + . chr(0x80 | (($char >> 6) & 0x3F)) + . chr(0x80 | ($char & 0x3F)); + + case (0xFFFF & $bytes) == $bytes: + // return a 3-byte UTF-8 character + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0xE0 | (($bytes >> 12) & 0x0F)) + . chr(0x80 | (($bytes >> 6) & 0x3F)) + . chr(0x80 | ($bytes & 0x3F)); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * convert a string from one UTF-8 char to one UTF-16 char + * + * Normally should be handled by mb_convert_encoding, but + * provides a slower PHP-only method for installations + * that lack the multibye string extension. + * + * @param string $utf8 UTF-8 character + * @return string UTF-16 character + * @access private + */ + function utf82utf16($utf8) + { + // oh please oh please oh please oh please oh please + if(function_exists('mb_convert_encoding')) { + return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); + } + + switch(strlen($utf8)) { + case 1: + // this case should never be reached, because we are in ASCII range + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return $utf8; + + case 2: + // return a UTF-16 character from a 2-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr(0x07 & (ord($utf8{0}) >> 2)) + . chr((0xC0 & (ord($utf8{0}) << 6)) + | (0x3F & ord($utf8{1}))); + + case 3: + // return a UTF-16 character from a 3-byte UTF-8 char + // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + return chr((0xF0 & (ord($utf8{0}) << 4)) + | (0x0F & (ord($utf8{1}) >> 2))) + . chr((0xC0 & (ord($utf8{1}) << 6)) + | (0x7F & ord($utf8{2}))); + + case 4: + // return a UTF-16 surrogate pair from a 4-byte UTF-8 char + if(ord($utf8{0}) > 0xF4) return ''; # invalid + $char = ((0x1C0000 & (ord($utf8{0}) << 18)) + | (0x03F000 & (ord($utf8{1}) << 12)) + | (0x000FC0 & (ord($utf8{2}) << 6)) + | (0x00003F & ord($utf8{3}))); + if($char > 0x10FFFF) return ''; # invalid + $char -= 0x10000; + return chr(0xD8 | (($char >> 18) & 0x03)) + . chr(($char >> 10) & 0xFF) + . chr(0xDC | (($char >> 8) & 0x03)) + . chr($char & 0xFF); + } + + // ignoring UTF-32 for now, sorry + return ''; + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * @param bool $pretty pretty-print output with indents and newlines + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access public + */ + function encode($var, $pretty=false) + { + $this->indent = 0; + $this->pretty = $pretty; + $this->nameValSeparator = $pretty ? ': ' : ':'; + return $this->encode2($var); + } + + /** + * encodes an arbitrary variable into JSON format + * + * @param mixed $var any number, boolean, string, array, or object to be encoded. + * see argument 1 to Services_JSON() above for array-parsing behavior. + * if var is a strng, note that encode() always expects it + * to be in ASCII or UTF-8 format! + * + * @return mixed JSON string representation of input var or an error if a problem occurs + * @access private + */ + function encode2($var) + { + if ($this->pretty) { + $close = "\n" . str_repeat("\t", $this->indent); + $open = $close . "\t"; + $mid = ',' . $open; + } + else { + $open = $close = ''; + $mid = ','; + } + + switch (gettype($var)) { + case 'boolean': + return $var ? 'true' : 'false'; + + case 'NULL': + return 'null'; + + case 'integer': + return (int) $var; + + case 'double': + case 'float': + return (float) $var; + + case 'string': + // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT + $ascii = ''; + $strlen_var = strlen($var); + + /* + * Iterate over every character in the string, + * escaping with a slash or encoding to UTF-8 where necessary + */ + for ($c = 0; $c < $strlen_var; ++$c) { + + $ord_var_c = ord($var{$c}); + + switch (true) { + case $ord_var_c == 0x08: + $ascii .= '\b'; + break; + case $ord_var_c == 0x09: + $ascii .= '\t'; + break; + case $ord_var_c == 0x0A: + $ascii .= '\n'; + break; + case $ord_var_c == 0x0C: + $ascii .= '\f'; + break; + case $ord_var_c == 0x0D: + $ascii .= '\r'; + break; + + case $ord_var_c == 0x22: + case $ord_var_c == 0x2F: + case $ord_var_c == 0x5C: + // double quote, slash, slosh + $ascii .= '\\'.$var{$c}; + break; + + case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): + // characters U-00000000 - U-0000007F (same as ASCII) + $ascii .= $var{$c}; + break; + + case (($ord_var_c & 0xE0) == 0xC0): + // characters U-00000080 - U-000007FF, mask 110XXXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, ord($var{$c + 1})); + $c += 1; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF0) == 0xE0): + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2})); + $c += 2; + $utf16 = $this->utf82utf16($char); + $ascii .= sprintf('\u%04s', bin2hex($utf16)); + break; + + case (($ord_var_c & 0xF8) == 0xF0): + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + // These will always return a surrogate pair + $char = pack('C*', $ord_var_c, + ord($var{$c + 1}), + ord($var{$c + 2}), + ord($var{$c + 3})); + $c += 3; + $utf16 = $this->utf82utf16($char); + if($utf16 == '') { + $ascii .= '\ufffd'; + } else { + $utf16 = str_split($utf16, 2); + $ascii .= sprintf('\u%04s\u%04s', bin2hex($utf16[0]), bin2hex($utf16[1])); + } + break; + } + } + + return '"'.$ascii.'"'; + + case 'array': + /* + * As per JSON spec if any array key is not an integer + * we must treat the the whole array as an object. We + * also try to catch a sparsely populated associative + * array with numeric keys here because some JS engines + * will create an array with empty indexes up to + * max_index which can cause memory issues and because + * the keys, which may be relevant, will be remapped + * otherwise. + * + * As per the ECMA and JSON specification an object may + * have any string as a property. Unfortunately due to + * a hole in the ECMA specification if the key is a + * ECMA reserved word or starts with a digit the + * parameter is only accessible using ECMAScript's + * bracket notation. + */ + + // treat as a JSON object + if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { + $this->indent++; + $properties = array_map(array($this, 'name_value'), + array_keys($var), + array_values($var)); + $this->indent--; + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . $open . join($mid, $properties) . $close . '}'; + } + + // treat it like a regular array + $this->indent++; + $elements = array_map(array($this, 'encode2'), $var); + $this->indent--; + + foreach($elements as $element) { + if(Services_JSON::isError($element)) { + return $element; + } + } + + return '[' . $open . join($mid, $elements) . $close . ']'; + + case 'object': + $vars = get_object_vars($var); + + $this->indent++; + $properties = array_map(array($this, 'name_value'), + array_keys($vars), + array_values($vars)); + $this->indent--; + + foreach($properties as $property) { + if(Services_JSON::isError($property)) { + return $property; + } + } + + return '{' . $open . join($mid, $properties) . $close . '}'; + + default: + return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) + ? 'null' + : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); + } + } + + /** + * array-walking function for use in generating JSON-formatted name-value pairs + * + * @param string $name name of key to use + * @param mixed $value reference to an array element to be encoded + * + * @return string JSON-formatted name-value pair, like '"name":value' + * @access private + */ + function name_value($name, $value) + { + $encoded_value = $this->encode2($value); + + if(Services_JSON::isError($encoded_value)) { + return $encoded_value; + } + + return $this->encode2(strval($name)) . $this->nameValSeparator . $encoded_value; + } + + /** + * reduce a string by removing leading and trailing comments and whitespace + * + * @param $str string string value to strip of comments and whitespace + * + * @return string string value stripped of comments and whitespace + * @access private + */ + function reduce_string($str) + { + $str = preg_replace(array( + + // eliminate single line comments in '// ...' form + '#^\s*//(.+)$#m', + + // eliminate multi-line comments in '/* ... */' form, at start of string + '#^\s*/\*(.+)\*/#Us', + + // eliminate multi-line comments in '/* ... */' form, at end of string + '#/\*(.+)\*/\s*$#Us' + + ), '', $str); + + // eliminate extraneous space + return trim($str); + } + + /** + * decodes a JSON string into appropriate variable + * + * @param string $str JSON-formatted string + * + * @return mixed number, boolean, string, array, or object + * corresponding to given JSON input string. + * See argument 1 to Services_JSON() above for object-output behavior. + * Note that decode() always returns strings + * in ASCII or UTF-8 format! + * @access public + */ + function decode($str) + { + $str = $this->reduce_string($str); + + switch (strtolower($str)) { + case 'true': + return true; + + case 'false': + return false; + + case 'null': + return null; + + default: + $m = array(); + + if (is_numeric($str)) { + // Lookie-loo, it's a number + + // This would work on its own, but I'm trying to be + // good about returning integers where appropriate: + // return (float)$str; + + // Return float or int, as appropriate + return ((float)$str == (integer)$str) + ? (integer)$str + : (float)$str; + + } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { + // STRINGS RETURNED IN UTF-8 FORMAT + $delim = substr($str, 0, 1); + $chrs = substr($str, 1, -1); + $utf8 = ''; + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c < $strlen_chrs; ++$c) { + + $substr_chrs_c_2 = substr($chrs, $c, 2); + $ord_chrs_c = ord($chrs{$c}); + + switch (true) { + case $substr_chrs_c_2 == '\b': + $utf8 .= chr(0x08); + ++$c; + break; + case $substr_chrs_c_2 == '\t': + $utf8 .= chr(0x09); + ++$c; + break; + case $substr_chrs_c_2 == '\n': + $utf8 .= chr(0x0A); + ++$c; + break; + case $substr_chrs_c_2 == '\f': + $utf8 .= chr(0x0C); + ++$c; + break; + case $substr_chrs_c_2 == '\r': + $utf8 .= chr(0x0D); + ++$c; + break; + + case $substr_chrs_c_2 == '\\"': + case $substr_chrs_c_2 == '\\\'': + case $substr_chrs_c_2 == '\\\\': + case $substr_chrs_c_2 == '\\/': + if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || + ($delim == "'" && $substr_chrs_c_2 != '\\"')) { + $utf8 .= $chrs{++$c}; + } + break; + + case preg_match('/\\\uD[89AB][0-9A-F]{2}\\\uD[C-F][0-9A-F]{2}/i', substr($chrs, $c, 12)): + // escaped unicode surrogate pair + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))) + . chr(hexdec(substr($chrs, ($c + 8), 2))) + . chr(hexdec(substr($chrs, ($c + 10), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 11; + break; + + case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): + // single, escaped unicode character + $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) + . chr(hexdec(substr($chrs, ($c + 4), 2))); + $utf8 .= $this->utf162utf8($utf16); + $c += 5; + break; + + case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): + $utf8 .= $chrs{$c}; + break; + + case ($ord_chrs_c & 0xE0) == 0xC0: + // characters U-00000080 - U-000007FF, mask 110XXXXX + //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 2); + ++$c; + break; + + case ($ord_chrs_c & 0xF0) == 0xE0: + // characters U-00000800 - U-0000FFFF, mask 1110XXXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 3); + $c += 2; + break; + + case ($ord_chrs_c & 0xF8) == 0xF0: + // characters U-00010000 - U-001FFFFF, mask 11110XXX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 4); + $c += 3; + break; + + case ($ord_chrs_c & 0xFC) == 0xF8: + // characters U-00200000 - U-03FFFFFF, mask 111110XX + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 5); + $c += 4; + break; + + case ($ord_chrs_c & 0xFE) == 0xFC: + // characters U-04000000 - U-7FFFFFFF, mask 1111110X + // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 + $utf8 .= substr($chrs, $c, 6); + $c += 5; + break; + + } + + } + + return $utf8; + + } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { + // array, or object notation + + if ($str{0} == '[') { + $stk = array(SERVICES_JSON_IN_ARR); + $arr = array(); + } else { + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = array(); + } else { + $stk = array(SERVICES_JSON_IN_OBJ); + $obj = new stdClass(); + } + } + + array_push($stk, array( 'what' => SERVICES_JSON_SLICE, + 'where' => 0, + 'delim' => false)); + + $chrs = substr($str, 1, -1); + $chrs = $this->reduce_string($chrs); + + if ($chrs == '') { + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } else { + return $obj; + + } + } + + //print("\nparsing {$chrs}\n"); + + $strlen_chrs = strlen($chrs); + + for ($c = 0; $c <= $strlen_chrs; ++$c) { + + $top = end($stk); + $substr_chrs_c_2 = substr($chrs, $c, 2); + + if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { + // found a comma that is not inside a string, array, etc., + // OR we've reached the end of the character list + $slice = substr($chrs, $top['where'], ($c - $top['where'])); + array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); + //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + // we are in an array, so just push an element onto the stack + array_push($arr, $this->decode($slice)); + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + // we are in an object, so figure + // out the property name and set an + // element in an associative array, + // for now + $parts = array(); + + if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // "name":value pair + $key = $this->decode($parts[1]); + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { + // name:value pair, where name is unquoted + $key = $parts[1]; + $val = $this->decode($parts[2]); + + if ($this->use & SERVICES_JSON_LOOSE_TYPE) { + $obj[$key] = $val; + } else { + $obj->$key = $val; + } + } + + } + + } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { + // found a quote, and we are not inside a string + array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); + //print("Found start of string at {$c}\n"); + + } elseif (($chrs{$c} == $top['delim']) && + ($top['what'] == SERVICES_JSON_IN_STR) && + (($chrs{$c - 1} != '\\') || + ($chrs{$c - 1} == '\\' && $chrs{$c - 2} == '\\'))) { + // found a quote, we're in a string, and it's not escaped + array_pop($stk); + //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '[') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-bracket, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); + //print("Found start of array at {$c}\n"); + + } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { + // found a right-bracket, and we're in an array + array_pop($stk); + //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($chrs{$c} == '{') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a left-brace, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); + //print("Found start of object at {$c}\n"); + + } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { + // found a right-brace, and we're in an object + array_pop($stk); + //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } elseif (($substr_chrs_c_2 == '/*') && + in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { + // found a comment start, and we are in an array, object, or slice + array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); + $c++; + //print("Found start of comment at {$c}\n"); + + } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { + // found a comment end, and we're in one now + array_pop($stk); + $c++; + + for ($i = $top['where']; $i <= $c; ++$i) + $chrs = substr_replace($chrs, ' ', $i, 1); + + //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); + + } + + } + + if (reset($stk) == SERVICES_JSON_IN_ARR) { + return $arr; + + } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { + return $obj; + + } + + } + } + } + + /** + * @todo Ultimately, this should just call PEAR::isError() + */ + function isError($data, $code = null) + { + if (class_exists('pear')) { + return PEAR::isError($data, $code); + } elseif (is_object($data) && (get_class($data) == 'services_json_error' || + is_subclass_of($data, 'services_json_error'))) { + return true; + } + + return false; + } } @@ -831,31 +831,31 @@ class Services_JSON /// @cond if (class_exists('PEAR_Error')) { - /** - * @ingroup API - */ - class Services_JSON_Error extends PEAR_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - parent::PEAR_Error($message, $code, $mode, $options, $userinfo); - } - } + /** + * @ingroup API + */ + class Services_JSON_Error extends PEAR_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + parent::PEAR_Error($message, $code, $mode, $options, $userinfo); + } + } } else { /// @endcond - /** - * @todo Ultimately, this class shall be descended from PEAR_Error - * @ingroup API - */ - class Services_JSON_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - - } - } + /** + * @todo Ultimately, this class shall be descended from PEAR_Error + * @ingroup API + */ + class Services_JSON_Error + { + function Services_JSON_Error($message = 'unknown error', $code = null, + $mode = null, $options = null, $userinfo = null) + { + + } + } } diff --git a/includes/api/ApiFormatRaw.php b/includes/api/ApiFormatRaw.php new file mode 100644 index 00000000..51025448 --- /dev/null +++ b/includes/api/ApiFormatRaw.php @@ -0,0 +1,71 @@ +<?php + +/* + * Created on Feb 2, 2009 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiFormatBase.php'); +} + +/** + * Formatter that spits out anything you like with any desired MIME type + * @ingroup API + */ +class ApiFormatRaw extends ApiFormatBase { + + /** + * Constructor + * @param $main ApiMain object + * @param $errorFallback Formatter object to fall back on for errors + */ + public function __construct($main, $errorFallback) { + parent :: __construct($main, 'raw'); + $this->mErrorFallback = $errorFallback; + } + + public function getMimeType() { + $data = $this->getResultData(); + if(isset($data['error'])) + return $this->mErrorFallback->getMimeType(); + if(!isset($data['mime'])) + ApiBase::dieDebug(__METHOD__, "No MIME type set for raw formatter"); + return $data['mime']; + } + + public function execute() { + $data = $this->getResultData(); + if(isset($data['error'])) + { + $this->mErrorFallback->execute(); + return; + } + if(!isset($data['text'])) + ApiBase::dieDebug(__METHOD__, "No text given for raw formatter"); + $this->printText($data['text']); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFormatRaw.php 48629 2009-03-20 11:40:54Z catrope $'; + } +} diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index e741c16d..a716373d 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -42,7 +42,13 @@ class ApiFormatWddx extends ApiFormatBase { } public function execute() { - if (function_exists('wddx_serialize_value') && !$this->getIsHtml()) { + // Some versions of PHP have a broken wddx_serialize_value, see + // PHP bug 45314. Test encoding an affected character (U+00A0) + // to avoid this. + $expected = "<wddxPacket version='1.0'><header/><data><string>\xc2\xa0</string></data></wddxPacket>"; + if (function_exists('wddx_serialize_value') + && !$this->getIsHtml() + && wddx_serialize_value("\xc2\xa0") == $expected) { $this->printText(wddx_serialize_value($this->getResultData())); } else { // Don't do newlines and indentation if we weren't asked @@ -60,8 +66,8 @@ class ApiFormatWddx extends ApiFormatBase { } /** - * Recursivelly go through the object and output its data in WDDX format. - */ + * Recursively go through the object and output its data in WDDX format. + */ function slowWddxPrinter($elemValue, $indent = 0) { $indstr = ($this->getIsHtml() ? "" : str_repeat(' ', $indent)); $indstr2 = ($this->getIsHtml() ? "" : str_repeat(' ', $indent + 2)); @@ -109,6 +115,6 @@ class ApiFormatWddx extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatWddx.php 44588 2008-12-14 19:14:21Z demon $'; + return __CLASS__ . ': $Id: ApiFormatWddx.php 48716 2009-03-23 20:06:16Z catrope $'; } } diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index 7ff57324..35b412c9 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -89,6 +89,11 @@ class ApiFormatXml extends ApiFormatBase { if ($this->mDoubleQuote) $subElemContent = $this->doubleQuote($subElemContent); unset ($elemValue['*']); + + // Add xml:space="preserve" to the + // element so XML parsers will leave + // whitespace in the content alone + $elemValue['xml:space'] = 'preserve'; } else { $subElemContent = null; } @@ -106,14 +111,6 @@ class ApiFormatXml extends ApiFormatBase { if (is_string($subElemValue) && $this->mDoubleQuote) $subElemValue = $this->doubleQuote($subElemValue); - // Replace spaces with underscores - $newSubElemId = str_replace(' ', '_', $subElemId); - if($newSubElemId != $subElemId) { - $elemValue[$newSubElemId] = $subElemValue; - unset($elemValue[$subElemId]); - $subElemId = $newSubElemId; - } - if (gettype($subElemId) === 'integer') { $indElements[] = $subElemValue; unset ($elemValue[$subElemId]); @@ -175,6 +172,6 @@ class ApiFormatXml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 44588 2008-12-14 19:14:21Z demon $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 50217 2009-05-05 13:12:16Z tstarling $'; } } diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index 4ccb5acf..c001a7dc 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -50,6 +50,10 @@ class ApiHelp extends ApiBase { return false; } + public function isReadMode() { + return false; + } + public function getDescription() { return array ( 'Display this help screen.' @@ -57,6 +61,6 @@ class ApiHelp extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiHelp.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiHelp.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php new file mode 100644 index 00000000..4b1518bb --- /dev/null +++ b/includes/api/ApiImport.php @@ -0,0 +1,179 @@ +<?php + +/* + * Created on Feb 4, 2009 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiBase.php'); +} + +/** + * API module that imports an XML file like Special:Import does + * + * @ingroup API + */ +class ApiImport extends ApiBase { + + public function __construct($main, $action) { + parent :: __construct($main, $action); + } + + public function execute() { + global $wgUser; + if(!$wgUser->isAllowed('import')) + $this->dieUsageMsg(array('cantimport')); + $params = $this->extractRequestParams(); + if(!isset($params['token'])) + $this->dieUsageMsg(array('missingparam', 'token')); + if(!$wgUser->matchEditToken($params['token'])) + $this->dieUsageMsg(array('sessionfailure')); + + $source = null; + $isUpload = false; + if(isset($params['interwikisource'])) + { + if(!isset($params['interwikipage'])) + $this->dieUsageMsg(array('missingparam', 'interwikipage')); + $source = ImportStreamSource::newFromInterwiki( + $params['interwikisource'], + $params['interwikipage'], + $params['fullhistory'], + $params['templates']); + } + else + { + $isUpload = true; + if(!$wgUser->isAllowed('importupload')) + $this->dieUsageMsg(array('cantimport-upload')); + $source = ImportStreamSource::newFromUpload('xml'); + } + if($source instanceof WikiErrorMsg) + $this->dieUsageMsg(array_merge( + array($source->getMessageKey()), + $source->getMessageArgs())); + else if(WikiError::isError($source)) + // This shouldn't happen + $this->dieUsageMsg(array('import-unknownerror', $source->getMessage())); + + $importer = new WikiImporter($source); + if(isset($params['namespace'])) + $importer->setTargetNamespace($params['namespace']); + $reporter = new ApiImportReporter($importer, $isUpload, + $params['interwikisource'], + $params['summary']); + + $result = $importer->doImport(); + if($result instanceof WikiXmlError) + $this->dieUsageMsg(array('import-xml-error', + $result->mLine, + $result->mColumn, + $result->mByte . $result->mContext, + xml_error_string($result->mXmlError))); + else if(WikiError::isError($result)) + // This shouldn't happen + $this->dieUsageMsg(array('import-unknownerror', $result->getMessage())); + $resultData = $reporter->getData(); + $this->getResult()->setIndexedTagName($resultData, 'page'); + $this->getResult()->addValue(null, $this->getModuleName(), $resultData); + } + + public function mustBePosted() { return true; } + + public function isWriteMode() { + return true; + } + + public function getAllowedParams() { + global $wgImportSources; + return array ( + 'token' => null, + 'summary' => null, + 'xml' => null, + 'interwikisource' => array( + ApiBase :: PARAM_TYPE => $wgImportSources + ), + 'interwikipage' => null, + 'fullhistory' => false, + 'templates' => false, + 'namespace' => array( + ApiBase :: PARAM_TYPE => 'namespace' + ) + ); + } + + public function getParamDescription() { + return array ( + 'token' => 'Import token obtained through prop=info', + 'summary' => 'Import summary', + 'xml' => 'Uploaded XML file', + 'interwikisource' => 'For interwiki imports: wiki to import from', + 'interwikipage' => 'For interwiki imports: page to import', + 'fullhistory' => 'For interwiki imports: import the full history, not just the current version', + 'templates' => 'For interwiki imports: import all included templates as well', + 'namespace' => 'For interwiki imports: import to this namespace', + ); + } + + public function getDescription() { + return array ( + 'Import a page from another wiki, or an XML file' + ); + } + + protected function getExamples() { + return array( + 'Import [[meta:Help:Parserfunctions]] to namespace 100 with full history:', + ' api.php?action=import&interwikisource=meta&interwikipage=Help:ParserFunctions&namespace=100&fullhistory&token=123ABC', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiImport.php 48091 2009-03-06 13:49:44Z catrope $'; + } +} + +/** + * Import reporter for the API + * @ingroup API + */ +class ApiImportReporter extends ImportReporter { + private $mResultArr = array(); + + function reportPage($title, $origTitle, $revisionCount, $successCount) + { + // Add a result entry + $r = array(); + ApiQueryBase::addTitleInfo($r, $title); + $r['revisions'] = intval($successCount); + $this->mResultArr[] = $r; + + // Piggyback on the parent to do the logging + parent::reportPage($title, $origTitle, $revisionCount, $successCount); + } + + function getData() + { + return $this->mResultArr; + } +} diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index 43b30f7c..bc477e1d 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -82,7 +82,7 @@ class ApiLogin extends ApiBase { wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); $result['result'] = 'Success'; - $result['lguserid'] = $wgUser->getId(); + $result['lguserid'] = intval($wgUser->getId()); $result['lgusername'] = $wgUser->getName(); $result['lgtoken'] = $wgUser->getToken(); $result['cookieprefix'] = $wgCookiePrefix; @@ -114,7 +114,7 @@ class ApiLogin extends ApiBase { case LoginForm :: THROTTLED : global $wgPasswordAttemptThrottle; $result['result'] = 'Throttled'; - $result['wait'] = $wgPasswordAttemptThrottle['seconds']; + $result['wait'] = intval($wgPasswordAttemptThrottle['seconds']); break; default : ApiBase :: dieDebug(__METHOD__, "Unhandled case value: {$authRes}"); @@ -125,6 +125,10 @@ class ApiLogin extends ApiBase { public function mustBePosted() { return true; } + public function isReadMode() { + return false; + } + public function getAllowedParams() { return array ( 'name' => null, @@ -158,6 +162,6 @@ class ApiLogin extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 45275 2009-01-01 02:02:03Z simetrical $'; + return __CLASS__ . ': $Id: ApiLogin.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php index 8b178f6a..0af579ca 100644 --- a/includes/api/ApiLogout.php +++ b/includes/api/ApiLogout.php @@ -50,6 +50,10 @@ class ApiLogout extends ApiBase { wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) ); } + public function isReadMode() { + return false; + } + public function getAllowedParams() { return array (); } @@ -71,6 +75,6 @@ class ApiLogout extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiLogout.php 43522 2008-11-15 01:23:39Z brion $'; + return __CLASS__ . ': $Id: ApiLogout.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 60d932be..ffdeb1e8 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -65,10 +65,9 @@ class ApiMain extends ApiBase { 'feedwatchlist' => 'ApiFeedWatchlist', 'help' => 'ApiHelp', 'paraminfo' => 'ApiParamInfo', - 'purge' => 'ApiPurge', - ); - private static $WriteModules = array ( + // Write modules + 'purge' => 'ApiPurge', 'rollback' => 'ApiRollback', 'delete' => 'ApiDelete', 'undelete' => 'ApiUndelete', @@ -80,6 +79,7 @@ class ApiMain extends ApiBase { 'emailuser' => 'ApiEmailUser', 'watch' => 'ApiWatch', 'patrol' => 'ApiPatrol', + 'import' => 'ApiImport', ); /** @@ -149,20 +149,10 @@ class ApiMain extends ApiBase { wfDebug( "API: stripping user credentials for JSON callback\n" ); $wgUser = new User(); } - - if (!$wgUser->isAllowed('read')) { - self::$Modules = array( - 'login' => self::$Modules['login'], - 'logout' => self::$Modules['logout'], - 'help' => self::$Modules['help'], - ); - } } - global $wgAPIModules, $wgEnableWriteAPI; // extension modules + global $wgAPIModules; // extension modules $this->mModules = $wgAPIModules + self :: $Modules; - if($wgEnableWriteAPI) - $this->mModules += self::$WriteModules; $this->mModuleNames = array_keys($this->mModules); $this->mFormats = self :: $Formats; @@ -200,22 +190,10 @@ class ApiMain extends ApiBase { } /** - * This method will simply cause an error if the write mode was disabled - * or if the current user doesn't have the right to use it + * Only kept for backwards compatibility + * @deprecated Use isWriteMode() instead */ - public function requestWriteMode() { - global $wgUser; - if (!$this->mEnableWrite) - $this->dieUsage('Editing of this wiki through the API' . - ' is disabled. Make sure the $wgEnableWriteAPI=true; ' . - 'statement is included in the wiki\'s ' . - 'LocalSettings.php file', 'noapiwrite'); - if (!$wgUser->isAllowed('writeapi')) - $this->dieUsage('You\'re not allowed to edit this ' . - 'wiki through the API', 'writeapidenied'); - if (wfReadOnly()) - $this->dieUsageMsg(array('readonlytext')); - } + public function requestWriteMode() {} /** * Set how long the response should be cached. @@ -360,9 +338,11 @@ class ApiMain extends ApiBase { } $this->getResult()->reset(); + $this->getResult()->disableSizeCheck(); // Re-add the id - if($this->mRequest->getCheck('requestid')) - $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); + $requestid = $this->getParameter('requestid'); + if(!is_null($requestid)) + $this->getResult()->addValue(null, 'requestid', $requestid); $this->getResult()->addValue(null, 'error', $errMessage); return $errMessage['code']; @@ -373,8 +353,9 @@ class ApiMain extends ApiBase { */ protected function executeAction() { // First add the id to the top element - if($this->mRequest->getCheck('requestid')) - $this->getResult()->addValue(null, 'requestid', $this->mRequest->getVal('requestid')); + $requestid = $this->getParameter('requestid'); + if(!is_null($requestid)) + $this->getResult()->addValue(null, 'requestid', $requestid); $params = $this->extractRequestParams(); @@ -398,14 +379,26 @@ class ApiMain extends ApiBase { header( 'X-Database-Lag: ' . intval( $lag ) ); // XXX: should we return a 503 HTTP error code like wfMaxlagError() does? if( $wgShowHostnames ) { - ApiBase :: dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); + $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); } else { - ApiBase :: dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); + $this->dieUsage( "Waiting for a database server: $lag seconds lagged", 'maxlag' ); } return; } } + global $wgUser; + if ($module->isReadMode() && !$wgUser->isAllowed('read')) + $this->dieUsageMsg(array('readrequired')); + if ($module->isWriteMode()) { + if (!$this->mEnableWrite) + $this->dieUsageMsg(array('writedisabled')); + if (!$wgUser->isAllowed('writeapi')) + $this->dieUsageMsg(array('writerequired')); + if (wfReadOnly()) + $this->dieUsageMsg(array('readonlytext')); + } + if (!$this->mInternalMode) { // Ignore mustBePosted() for internal calls if($module->mustBePosted() && !$this->mRequest->wasPosted()) @@ -438,7 +431,7 @@ class ApiMain extends ApiBase { * Print results using the current printer */ protected function printResult($isError) { - $this->getResult()->cleanupUTF8(); + $this->getResult()->cleanUpUTF8(); $printer = $this->mPrinter; $printer->profileIn(); @@ -454,6 +447,10 @@ class ApiMain extends ApiBase { $printer->closePrinter(); $printer->profileOut(); } + + public function isReadMode() { + return false; + } /** * See ApiBase for description. @@ -657,7 +654,7 @@ class ApiMain extends ApiBase { public function getVersion() { $vers = array (); $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; - $vers[] = __CLASS__ . ': $Id: ApiMain.php 45752 2009-01-14 21:36:57Z catrope $'; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 50834 2009-05-20 20:10:47Z catrope $'; $vers[] = ApiBase :: getBaseVersion(); $vers[] = ApiFormatBase :: getBaseVersion(); $vers[] = ApiQueryBase :: getBaseVersion(); diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index 13b058c9..e22d0294 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -39,7 +39,6 @@ class ApiMove extends ApiBase { public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); if(is_null($params['reason'])) $params['reason'] = ''; @@ -73,6 +72,7 @@ class ApiMove extends ApiBase { $this->dieUsageMsg(array('invalidtitle', $params['to'])); $toTalk = $toTitle->getTalkPage(); + # Move the page $hookErr = null; $retval = $fromTitle->moveTo($toTitle, true, $params['reason'], !$params['noredirect']); if($retval !== true) @@ -82,10 +82,9 @@ class ApiMove extends ApiBase { if(!$params['noredirect'] || !$wgUser->isAllowed('suppressredirect')) $r['redirectcreated'] = ''; + # Move the talk page if($params['movetalk'] && $fromTalk->exists() && !$fromTitle->isTalkPage()) { - // We need to move the talk page as well - $toTalk = $toTitle->getTalkPage(); $retval = $fromTalk->moveTo($toTalk, true, $params['reason'], !$params['noredirect']); if($retval === true) { @@ -101,6 +100,20 @@ class ApiMove extends ApiBase { } } + # Move subpages + if($params['movesubpages']) + { + $r['subpages'] = $this->moveSubpages($fromTitle, $toTitle, + $params['reason'], $params['noredirect']); + $this->getResult()->setIndexedTagName($r['subpages'], 'subpage'); + if($params['movetalk']) + { + $r['subpages-talk'] = $this->moveSubpages($fromTalk, $toTalk, + $params['reason'], $params['noredirect']); + $this->getResult()->setIndexedTagName($r['subpages-talk'], 'subpage'); + } + } + # Watch pages if($params['watch'] || $wgUser->getOption('watchmoves')) { @@ -114,9 +127,37 @@ class ApiMove extends ApiBase { } $this->getResult()->addValue(null, $this->getModuleName(), $r); } + + public function moveSubpages($fromTitle, $toTitle, $reason, $noredirect) + { + $retval = array(); + $success = $fromTitle->moveSubpages($toTitle, true, $reason, !$noredirect); + if(isset($success[0])) + return array('error' => $this->parseMsg($success)); + else + { + // At least some pages could be moved + // Report each of them separately + foreach($success as $oldTitle => $newTitle) + { + $r = array('from' => $oldTitle); + if(is_array($newTitle)) + $r['error'] = $this->parseMsg(reset($newTitle)); + else + // Success + $r['to'] = $newTitle; + $retval[] = $r; + } + } + return $retval; + } public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'from' => null, @@ -127,6 +168,7 @@ class ApiMove extends ApiBase { 'token' => null, 'reason' => null, 'movetalk' => false, + 'movesubpages' => false, 'noredirect' => false, 'watch' => false, 'unwatch' => false @@ -141,6 +183,7 @@ class ApiMove extends ApiBase { 'token' => 'A move token previously retrieved through prop=info', 'reason' => 'Reason for the move (optional).', 'movetalk' => 'Move the talk page, if it exists.', + 'movesubpages' => 'Move subpages, if applicable', 'noredirect' => 'Don\'t create a redirect', 'watch' => 'Add the page and the redirect to your watchlist', 'unwatch' => 'Remove the page and the redirect from your watchlist' @@ -160,6 +203,6 @@ class ApiMove extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiMove.php 47041 2009-02-09 14:39:41Z catrope $'; + return __CLASS__ . ': $Id: ApiMove.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index 2da92059..8fc1f32b 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -42,10 +42,14 @@ class ApiOpenSearch extends ApiBase { } public function execute() { + global $wgEnableMWSuggest; $params = $this->extractRequestParams(); $search = $params['search']; $limit = $params['limit']; $namespaces = $params['namespace']; + $suggest = $params['suggest']; + # $wgEnableMWSuggest hit incoming when $wgEnableMWSuggest is disabled + if( $suggest && !$wgEnableMWSuggest ) return; // Open search results may be stored for a very long time $this->getMain()->setCacheMaxAge(1200); @@ -61,7 +65,7 @@ class ApiOpenSearch extends ApiBase { public function getAllowedParams() { return array ( 'search' => null, - 'limit' => array ( + 'limit' => array( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', ApiBase :: PARAM_MIN => 1, @@ -73,6 +77,7 @@ class ApiOpenSearch extends ApiBase { ApiBase :: PARAM_TYPE => 'namespace', ApiBase :: PARAM_ISMULTI => true ), + 'suggest' => false, ); } @@ -81,6 +86,7 @@ class ApiOpenSearch extends ApiBase { 'search' => 'Search string', 'limit' => 'Maximum amount of results to return', 'namespace' => 'Namespaces to search', + 'suggest' => 'Do nothing if $wgEnableMWSuggest is false', ); } @@ -95,6 +101,6 @@ class ApiOpenSearch extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiOpenSearch.php 35098 2008-05-20 17:13:28Z ialex $'; + return __CLASS__ . ': $Id: ApiOpenSearch.php 47188 2009-02-12 17:27:05Z catrope $'; } } diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 54482e4b..6b9e90b8 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -30,13 +30,14 @@ if (!defined('MEDIAWIKI')) { /** * This class contains a list of pages that the client has requested. - * Initially, when the client passes in titles=, pageids=, or revisions= parameter, - * an instance of the ApiPageSet class will normalize titles, - * determine if the pages/revisions exist, and prefetch any additional data page data requested. + * Initially, when the client passes in titles=, pageids=, or revisions= + * parameter, an instance of the ApiPageSet class will normalize titles, + * determine if the pages/revisions exist, and prefetch any additional page + * data requested. * - * When generator is used, the result of the generator will become the input for the - * second instance of this class, and all subsequent actions will go use the second instance - * for all their work. + * When a generator is used, the result of the generator will become the input + * for the second instance of this class, and all subsequent actions will use + * the second instance for all their work. * * @ingroup API */ @@ -52,6 +53,11 @@ class ApiPageSet extends ApiQueryBase { private $mRequestedPageFields; + /** + * Constructor + * @param $query ApiQuery + * @param $resolveRedirects bool Whether redirects should be resolved + */ public function __construct($query, $resolveRedirects = false) { parent :: __construct($query, 'query'); @@ -75,20 +81,38 @@ class ApiPageSet extends ApiQueryBase { $this->mFakePageId = -1; } + /** + * Check whether this PageSet is resolving redirects + * @return bool + */ public function isResolvingRedirects() { return $this->mResolveRedirects; } + /** + * Request an additional field from the page table. Must be called + * before execute() + * @param $fieldName string Field name + */ public function requestField($fieldName) { $this->mRequestedPageFields[$fieldName] = null; } + /** + * Get the value of a custom field previously requested through + * requestField() + * @param $fieldName string Field name + * @return mixed Field value + */ public function getCustomField($fieldName) { return $this->mRequestedPageFields[$fieldName]; } /** - * Get fields that modules have requested from the page table + * Get the fields that have to be queried from the page table: + * the ones requested through requestField() and a few basic ones + * we always need + * @return array of field names */ public function getPageTableFields() { // Ensure we get minimum required fields @@ -99,12 +123,12 @@ class ApiPageSet extends ApiQueryBase { 'page_id' => null, ); - // only store non-default fields - $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds); - if ($this->mResolveRedirects) $pageFlds['page_is_redirect'] = null; + // only store non-default fields + $this->mRequestedPageFields = array_diff_key($this->mRequestedPageFields, $pageFlds); + $pageFlds = array_merge($pageFlds, $this->mRequestedPageFields); return array_keys($pageFlds); } @@ -113,6 +137,7 @@ class ApiPageSet extends ApiQueryBase { * Returns an array [ns][dbkey] => page_id for all requested titles. * page_id is a unique negative number in case title was not found. * Invalid titles will also have negative page IDs and will be in namespace 0 + * @return array */ public function getAllTitlesByNamespace() { return $this->mAllPages; @@ -128,6 +153,7 @@ class ApiPageSet extends ApiQueryBase { /** * Returns the number of unique pages (not revisions) in the set. + * @return int */ public function getTitleCount() { return count($this->mTitles); @@ -143,6 +169,7 @@ class ApiPageSet extends ApiQueryBase { /** * Returns the number of found unique pages (not revisions) in the set. + * @return int */ public function getGoodTitleCount() { return count($this->mGoodTitles); @@ -175,7 +202,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Get a list of redirects when doing redirect resolution + * Get a list of redirect resolutions - maps a title to its redirect + * target. * @return array prefixed_title (string) => prefixed_title (string) */ public function getRedirectTitles() { @@ -183,8 +211,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Get a list of title normalizations - maps the title given - * with its normalized version. + * Get a list of title normalizations - maps a title to its normalized + * version. * @return array raw_prefixed_title (string) => prefixed_title (string) */ public function getNormalizedTitles() { @@ -192,8 +220,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Get a list of interwiki titles - maps the title given - * with to the interwiki prefix. + * Get a list of interwiki titles - maps a title to its interwiki + * prefix. * @return array raw_prefixed_title (string) => interwiki_prefix (string) */ public function getInterwikiTitles() { @@ -201,7 +229,7 @@ class ApiPageSet extends ApiQueryBase { } /** - * Get the list of revision IDs (requested with revids= parameter) + * Get the list of revision IDs (requested with the revids= parameter) * @return array revID (int) => pageID (int) */ public function getRevisionIDs() { @@ -217,14 +245,15 @@ class ApiPageSet extends ApiQueryBase { } /** - * Returns the number of revisions (requested with revids= parameter) + * Returns the number of revisions (requested with revids= parameter)\ + * @return int */ public function getRevisionCount() { return count($this->getRevisionIDs()); } /** - * Populate from the request parameters + * Populate the PageSet from the request parameters. */ public function execute() { $this->profileIn(); @@ -267,7 +296,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Initialize PageSet from a list of Titles + * Populate this PageSet from a list of Titles + * @param $titles array of Title objects */ public function populateFromTitles($titles) { $this->profileIn(); @@ -276,7 +306,8 @@ class ApiPageSet extends ApiQueryBase { } /** - * Initialize PageSet from a list of Page IDs + * Populate this PageSet from a list of page IDs + * @param $pageIDs array of page IDs */ public function populateFromPageIDs($pageIDs) { $this->profileIn(); @@ -285,7 +316,9 @@ class ApiPageSet extends ApiQueryBase { } /** - * Initialize PageSet from a rowset returned from the database + * Populate this PageSet from a rowset returned from the database + * @param $db Database object + * @param $queryResult Query result object */ public function populateFromQueryResult($db, $queryResult) { $this->profileIn(); @@ -294,17 +327,18 @@ class ApiPageSet extends ApiQueryBase { } /** - * Initialize PageSet from a list of Revision IDs + * Populate this PageSet from a list of revision IDs + * @param $revIDs array of revision IDs */ public function populateFromRevisionIDs($revIDs) { $this->profileIn(); - $revIDs = array_map('intval', $revIDs); // paranoia $this->initFromRevIDs($revIDs); $this->profileOut(); } /** * Extract all requested fields from the row received from the database + * @param $row Result row */ public function processDbRow($row) { @@ -325,6 +359,9 @@ class ApiPageSet extends ApiQueryBase { $fieldValues[$pageId] = $row-> $fieldName; } + /** + * Resolve redirects, if applicable + */ public function finishPageSetGeneration() { $this->profileIn(); $this->resolvePendingRedirects(); @@ -341,9 +378,11 @@ class ApiPageSet extends ApiQueryBase { * * Additionally, when resolving redirects: * #3 If no more redirects left, stop. - * #4 For each redirect, get its links from `pagelinks` table. + * #4 For each redirect, get its target from the `redirect` table. * #5 Substitute the original LinkBatch object with the new list * #6 Repeat from step #1 + * + * @param $titles array of Title objects or strings */ private function initFromTitles($titles) { @@ -357,7 +396,8 @@ class ApiPageSet extends ApiQueryBase { // Get pageIDs data from the `page` table $this->profileDBIn(); - $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__); + $res = $db->select('page', $this->getPageTableFields(), $set, + __METHOD__); $this->profileDBOut(); // Hack: get the ns:titles stored in array(ns => array(titles)) format @@ -367,6 +407,10 @@ class ApiPageSet extends ApiQueryBase { $this->resolvePendingRedirects(); } + /** + * Does the same as initFromTitles(), but is based on page IDs instead + * @param $pageids array of page IDs + */ private function initFromPageIds($pageids) { if(!count($pageids)) return; @@ -375,12 +419,12 @@ class ApiPageSet extends ApiQueryBase { $set = array ( 'page_id' => $pageids ); - $db = $this->getDB(); // Get pageIDs data from the `page` table $this->profileDBIn(); - $res = $db->select('page', $this->getPageTableFields(), $set, __METHOD__); + $res = $db->select('page', $this->getPageTableFields(), $set, + __METHOD__); $this->profileDBOut(); $remaining = array_flip($pageids); @@ -395,12 +439,11 @@ class ApiPageSet extends ApiQueryBase { * and for each row create and store title object and save any extra fields requested. * @param $db Database * @param $res DB Query result - * @param $remaining Array of either pageID or ns/title elements (optional). + * @param $remaining array of either pageID or ns/title elements (optional). * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles * @param $processTitles bool Must be provided together with $remaining. * If true, treat $remaining as an array of [ns][title] * If false, treat it as an array of [pageIDs] - * @return Array of redirect IDs (only when resolving redirects) */ private function initFromQueryResult($db, $res, &$remaining = null, $processTitles = null) { if (!is_null($remaining) && is_null($processTitles)) @@ -448,30 +491,36 @@ class ApiPageSet extends ApiQueryBase { } } + /** + * Does the same as initFromTitles(), but is based on revision IDs + * instead + * @param $revids array of revision IDs + */ private function initFromRevIDs($revids) { if(!count($revids)) return; + $revids = array_map('intval', $revids); // paranoia $db = $this->getDB(); $pageids = array(); $remaining = array_flip($revids); - $tables = array('revision','page'); - $fields = array('rev_id','rev_page'); - $where = array('rev_deleted' => 0, 'rev_id' => $revids,'rev_page = page_id'); + $tables = array('revision', 'page'); + $fields = array('rev_id', 'rev_page'); + $where = array('rev_id' => $revids, 'rev_page = page_id'); // Get pageIDs data from the `page` table $this->profileDBIn(); - $res = $db->select( $tables, $fields, $where, __METHOD__ ); - while ( $row = $db->fetchObject( $res ) ) { + $res = $db->select($tables, $fields, $where, __METHOD__); + while ($row = $db->fetchObject($res)) { $revid = intval($row->rev_id); $pageid = intval($row->rev_page); $this->mGoodRevIDs[$revid] = $pageid; $pageids[$pageid] = ''; unset($remaining[$revid]); } - $db->freeResult( $res ); + $db->freeResult($res); $this->profileDBOut(); $this->mMissingRevIDs = array_keys($remaining); @@ -480,6 +529,11 @@ class ApiPageSet extends ApiQueryBase { $this->initFromPageIds(array_keys($pageids)); } + /** + * Resolve any redirects in the result if redirect resolution was + * requested. This function is called repeatedly until all redirects + * have been resolved. + */ private function resolvePendingRedirects() { if($this->mResolveRedirects) { @@ -498,7 +552,7 @@ class ApiPageSet extends ApiQueryBase { break; $set = $linkBatch->constructSet('page', $db); - if(false === $set) + if($set === false) break; // Get pageIDs data from the `page` table @@ -512,6 +566,13 @@ class ApiPageSet extends ApiQueryBase { } } + /** + * Get the targets of the pending redirects from the database + * + * Also creates entries in the redirect table for redirects that don't + * have one. + * @return LinkBatch + */ private function getRedirectTargets() { $lb = new LinkBatch(); $db = $this->getDB(); @@ -562,7 +623,8 @@ class ApiPageSet extends ApiQueryBase { * This method validates access rights for the title, * and appends normalization values to the output. * - * @return LinkBatch of title objects. + * @param $titles array of Title objects or strings + * @return LinkBatch */ private function processTitlesArray($titles) { @@ -592,9 +654,11 @@ class ApiPageSet extends ApiQueryBase { $linkBatch->addObj($titleObj); } - // Make sure we remember the original title that was given to us - // This way the caller can correlate new titles with the originally requested, - // i.e. namespace is localized or capitalization is different + // Make sure we remember the original title that was + // given to us. This way the caller can correlate new + // titles with the originally requested when e.g. the + // namespace is localized or the capitalization is + // different if (is_string($title) && $title !== $titleObj->getPrefixedText()) { $this->mNormalizedTitles[$title] = $titleObj->getPrefixedText(); } @@ -628,6 +692,6 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 45275 2009-01-01 02:02:03Z simetrical $'; + return __CLASS__ . ': $Id: ApiPageSet.php 47424 2009-02-18 05:29:11Z werdna $'; } } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index 2cf044cf..d710c206 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -41,6 +41,7 @@ class ApiParamInfo extends ApiBase { // Get parameters $params = $this->extractRequestParams(); $result = $this->getResult(); + $queryObj = new ApiQuery($this->getMain(), 'query'); $r = array(); if(is_array($params['modules'])) { @@ -61,7 +62,6 @@ class ApiParamInfo extends ApiBase { } if(is_array($params['querymodules'])) { - $queryObj = new ApiQuery($this->getMain(), 'query'); $qmodArr = $queryObj->getModules(); foreach($params['querymodules'] as $qm) { @@ -77,6 +77,13 @@ class ApiParamInfo extends ApiBase { } $result->setIndexedTagName($r['querymodules'], 'module'); } + if($params['mainmodule']) + $r['mainmodule'] = $this->getClassInfo($this->getMain()); + if($params['pagesetmodule']) + { + $pageSet = new ApiPageSet($queryObj); + $r['pagesetmodule'] = $this->getClassInfo($pageSet); + } $result->addValue(null, $this->getModuleName(), $r); } @@ -86,6 +93,12 @@ class ApiParamInfo extends ApiBase { $retval['classname'] = get_class($obj); $retval['description'] = (is_array($obj->getDescription()) ? implode("\n", $obj->getDescription()) : $obj->getDescription()); $retval['prefix'] = $obj->getModulePrefix(); + if($obj->isReadMode()) + $retval['readrights'] = ''; + if($obj->isWriteMode()) + $retval['writerights'] = ''; + if($obj->mustBePosted()) + $retval['mustbeposted'] = ''; $allowedParams = $obj->getFinalParams(); if(!is_array($allowedParams)) return $retval; @@ -140,6 +153,10 @@ class ApiParamInfo extends ApiBase { return $retval; } + public function isReadMode() { + return false; + } + public function getAllowedParams() { return array ( 'modules' => array( @@ -147,7 +164,9 @@ class ApiParamInfo extends ApiBase { ), 'querymodules' => array( ApiBase :: PARAM_ISMULTI => true - ) + ), + 'mainmodule' => false, + 'pagesetmodule' => false, ); } @@ -155,6 +174,8 @@ class ApiParamInfo extends ApiBase { return array ( 'modules' => 'List of module names (value of the action= parameter)', 'querymodules' => 'List of query module names (value of prop=, meta= or list= parameter)', + 'mainmodule' => 'Get information about the main (top-level) module as well', + 'pagesetmodule' => 'Get information about the pageset module (providing titles= and friends) as well', ); } @@ -169,6 +190,6 @@ class ApiParamInfo extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiParamInfo.php 41653 2008-10-04 15:03:03Z catrope $'; + return __CLASS__ . ': $Id: ApiParamInfo.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index e221fb1d..8f4b70bf 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -105,7 +105,7 @@ class ApiParse extends ApiBase { $p_result = $wgParser->parse($articleObj->getContent(), $titleObj, $popts); global $wgUseParserCache; if($wgUseParserCache) - $pcache->save($p_result, $articleObj, $wgUser); + $pcache->save($p_result, $articleObj, $popts); } } } @@ -151,8 +151,12 @@ class ApiParse extends ApiBase { $result_array['externallinks'] = array_keys($p_result->getExternalLinks()); if(isset($prop['sections'])) $result_array['sections'] = $p_result->getSections(); + if(isset($prop['displaytitle'])) + $result_array['displaytitle'] = $p_result->getDisplayTitle() ? + $p_result->getDisplayTitle() : + $titleObj->getPrefixedText(); if(!is_null($oldid)) - $result_array['revid'] = $oldid; + $result_array['revid'] = intval($oldid); $result_mapping = array( 'redirects' => 'r', @@ -223,7 +227,7 @@ class ApiParse extends ApiBase { 'redirects' => false, 'oldid' => null, 'prop' => array( - ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid', + ApiBase :: PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle', ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array( 'text', @@ -234,7 +238,8 @@ class ApiParse extends ApiBase { 'images', 'externallinks', 'sections', - 'revid' + 'revid', + 'displaytitle', ) ), 'pst' => false, @@ -272,6 +277,6 @@ class ApiParse extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiParse.php 44858 2008-12-20 20:00:07Z catrope $'; + return __CLASS__ . ': $Id: ApiParse.php 48544 2009-03-18 23:27:48Z aboostani $'; } } diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php index 08de87b0..2c9d1ecf 100644 --- a/includes/api/ApiPatrol.php +++ b/includes/api/ApiPatrol.php @@ -42,7 +42,6 @@ class ApiPatrol extends ApiBase { */ public function execute() { global $wgUser, $wgUseRCPatrol, $wgUseNPPatrol; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); if(!isset($params['token'])) @@ -58,13 +57,17 @@ class ApiPatrol extends ApiBase { $retval = RecentChange::markPatrolled($params['rcid']); if($retval) - $this->dieUsageMsg(current($retval)); + $this->dieUsageMsg(reset($retval)); - $result = array('rcid' => $rc->getAttribute('rc_id')); + $result = array('rcid' => intval($rc->getAttribute('rc_id'))); ApiQueryBase::addTitleInfo($result, $rc->getTitle()); $this->getResult()->addValue(null, $this->getModuleName(), $result); } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'token' => null, @@ -94,6 +97,6 @@ class ApiPatrol extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPatrol.php 42548 2008-10-25 14:04:43Z tstarling $'; + return __CLASS__ . ': $Id: ApiPatrol.php 48122 2009-03-07 12:58:41Z catrope $'; } } diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 522d02b2..aad37066 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -38,7 +38,6 @@ class ApiProtect extends ApiBase { public function execute() { global $wgUser, $wgRestrictionTypes, $wgRestrictionLevels; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); $titleObj = NULL; @@ -59,7 +58,7 @@ class ApiProtect extends ApiBase { $errors = $titleObj->getUserPermissionsErrors('protect', $wgUser); if($errors) // We don't care about multiple errors, just report one of them - $this->dieUsageMsg(current($errors)); + $this->dieUsageMsg(reset($errors)); $expiry = (array)$params['expiry']; if(count($expiry) != count($params['protections'])) @@ -106,10 +105,12 @@ class ApiProtect extends ApiBase { } $cascade = $params['cascade']; - if($titleObj->exists()) { - $articleObj = new Article($titleObj); + $articleObj = new Article($titleObj); + if($params['watch']) + $articleObj->doWatch(); + if($titleObj->exists()) $ok = $articleObj->updateRestrictions($protections, $params['reason'], $cascade, $expiryarray); - } else + else $ok = $titleObj->updateTitleProtection($protections['create'], $params['reason'], $expiryarray['create']); if(!$ok) // This is very weird. Maybe the article was deleted or the user was blocked/desysopped in the meantime? @@ -125,6 +126,10 @@ class ApiProtect extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'title' => null, @@ -138,7 +143,8 @@ class ApiProtect extends ApiBase { ApiBase :: PARAM_DFLT => 'infinite', ), 'reason' => '', - 'cascade' => false + 'cascade' => false, + 'watch' => false, ); } @@ -152,6 +158,7 @@ class ApiProtect extends ApiBase { 'reason' => 'Reason for (un)protecting (optional)', 'cascade' => array('Enable cascading protection (i.e. protect pages included in this page)', 'Ignored if not all protection levels are \'sysop\' or \'protect\''), + 'watch' => 'If set, add the page being (un)protected to your watchlist', ); } @@ -169,6 +176,6 @@ class ApiProtect extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiProtect.php 44426 2008-12-10 22:39:41Z catrope $'; + return __CLASS__ . ': $Id: ApiProtect.php 48122 2009-03-07 12:58:41Z catrope $'; } } diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php index d7202a46..27d5cac6 100644 --- a/includes/api/ApiPurge.php +++ b/includes/api/ApiPurge.php @@ -74,6 +74,15 @@ class ApiPurge extends ApiBase { $this->getResult()->addValue(null, $this->getModuleName(), $result); } + public function mustBePosted() { + global $wgUser; + return $wgUser->isAnon(); + } + + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'titles' => array( @@ -101,6 +110,6 @@ class ApiPurge extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPurge.php 41020 2008-09-19 00:21:03Z demon $'; + return __CLASS__ . ': $Id: ApiPurge.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index 45a5667a..149e4082 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -29,13 +29,13 @@ if (!defined('MEDIAWIKI')) { } /** - * This is the main query class. It behaves similar to ApiMain: based on the parameters given, - * it will create a list of titles to work on (an instance of the ApiPageSet object) - * instantiate and execute various property/list/meta modules, - * and assemble all resulting data into a single ApiResult object. + * This is the main query class. It behaves similar to ApiMain: based on the + * parameters given, it will create a list of titles to work on (an ApiPageSet + * object), instantiate and execute various property/list/meta modules, and + * assemble all resulting data into a single ApiResult object. * - * In the generator mode, a generator will be first executed to populate a second ApiPageSet object, - * and that object will be used for all subsequent modules. + * In generator mode, a generator will be executed first to populate a second + * ApiPageSet object, and that object will be used for all subsequent modules. * * @ingroup API */ @@ -80,6 +80,7 @@ class ApiQuery extends ApiBase { 'exturlusage' => 'ApiQueryExtLinksUsage', 'users' => 'ApiQueryUsers', 'random' => 'ApiQueryRandom', + 'protectedtitles' => 'ApiQueryProtectedTitles', ); private $mQueryMetaModules = array ( @@ -111,6 +112,8 @@ class ApiQuery extends ApiBase { /** * Helper function to append any add-in modules to the list + * @param $modules array Module array + * @param $newModules array Module array to add to $modules */ private static function appendUserModules(&$modules, $newModules) { if (is_array( $newModules )) { @@ -122,6 +125,7 @@ class ApiQuery extends ApiBase { /** * Gets a default slave database connection object + * @return Database */ public function getDB() { if (!isset ($this->mSlaveDB)) { @@ -136,7 +140,11 @@ class ApiQuery extends ApiBase { * Get the query database connection with the given name. * If no such connection has been requested before, it will be created. * Subsequent calls with the same $name will return the same connection - * as the first, regardless of $db or $groups new values. + * as the first, regardless of the values of $db and $groups + * @param $name string Name to assign to the database connection + * @param $db int One of the DB_* constants + * @param $groups array Query groups + * @return Database */ public function getNamedDB($name, $db, $groups) { if (!array_key_exists($name, $this->mNamedDB)) { @@ -149,6 +157,7 @@ class ApiQuery extends ApiBase { /** * Gets the set of pages the user has requested (or generated) + * @return ApiPageSet */ public function getPageSet() { return $this->mPageSet; @@ -156,15 +165,26 @@ class ApiQuery extends ApiBase { /** * Get the array mapping module names to class names + * @return array(modulename => classname) */ function getModules() { return array_merge($this->mQueryPropModules, $this->mQueryListModules, $this->mQueryMetaModules); } + + public function getCustomPrinter() { + // If &exportnowrap is set, use the raw formatter + if ($this->getParameter('export') && + $this->getParameter('exportnowrap')) + return new ApiFormatRaw($this->getMain(), + $this->getMain()->createPrinterByName('xml')); + else + return null; + } /** * Query execution happens in the following steps: * #1 Create a PageSet object with any pages requested by the user - * #2 If using generator, execute it to get a new PageSet object + * #2 If using a generator, execute it to get a new ApiPageSet object * #3 Instantiate all requested modules. * This way the PageSet object will know what shared data is required, * and minimize DB calls. @@ -220,6 +240,8 @@ class ApiQuery extends ApiBase { * Query modules may optimize data requests through the $this->getPageSet() object * by adding extra fields from the page table. * This function will gather all the extra request fields from the modules. + * @param $modules array of module objects + * @param $pageSet ApiPageSet */ private function addCustomFldsToPageSet($modules, $pageSet) { // Query all requested modules. @@ -230,6 +252,9 @@ class ApiQuery extends ApiBase { /** * Create instances of all modules requested by the client + * @param $modules array to append instatiated modules to + * @param $param string Parameter name to read modules from + * @param $moduleList array(modulename => classname) */ private function InstantiateModules(&$modules, $param, $moduleList) { $list = @$this->params[$param]; @@ -239,14 +264,19 @@ class ApiQuery extends ApiBase { } /** - * Appends an element for each page in the current pageSet with the most general - * information (id, title), plus any title normalizations and missing or invalid title/pageids/revids. + * Appends an element for each page in the current pageSet with the + * most general information (id, title), plus any title normalizations + * and missing or invalid title/pageids/revids. */ private function outputGeneralPageInfo() { $pageSet = $this->getPageSet(); $result = $this->getResult(); + # We don't check for a full result set here because we can't be adding + # more than 380K. The maximum revision size is in the megabyte range, + # and the maximum result size must be even higher than that. + // Title normalizations $normValues = array (); foreach ($pageSet->getNormalizedTitles() as $rawTitleStr => $titleStr) { @@ -346,12 +376,43 @@ class ApiQuery extends ApiBase { } $result->setIndexedTagName($pages, 'page'); - $result->addValue('query', 'pages', $pages); + $result->addValue('query', 'pages', $pages); + } + if ($this->params['export']) { + $exporter = new WikiExporter($this->getDB()); + // WikiExporter writes to stdout, so catch its + // output with an ob + ob_start(); + $exporter->openStream(); + foreach (@$pageSet->getGoodTitles() as $title) + if ($title->userCanRead()) + $exporter->pageByTitle($title); + $exporter->closeStream(); + $exportxml = ob_get_contents(); + ob_end_clean(); + // Don't check the size of exported stuff + // It's not continuable, so it would cause more + // problems than it'd solve + $result->disableSizeCheck(); + if ($this->params['exportnowrap']) { + $result->reset(); + // Raw formatter will handle this + $result->addValue(null, 'text', $exportxml); + $result->addValue(null, 'mime', 'text/xml'); + } else { + $r = array(); + ApiResult::setContent($r, $exportxml); + $result->addValue('query', 'export', $r); + } + $result->enableSizeCheck(); } } /** - * For generator mode, execute generator, and use its output as new pageSet + * For generator mode, execute generator, and use its output as new + * ApiPageSet + * @param $generatorName string Module name + * @param $modules array of module objects */ protected function executeGeneratorModule($generatorName, $modules) { @@ -392,10 +453,6 @@ class ApiQuery extends ApiBase { $this->mPageSet = $resultPageSet; } - /** - * Returns the list of allowed parameters for this module. - * Qurey module also lists all ApiPageSet parameters as its own. - */ public function getAllowedParams() { return array ( 'prop' => array ( @@ -415,11 +472,14 @@ class ApiQuery extends ApiBase { ), 'redirects' => false, 'indexpageids' => false, + 'export' => false, + 'exportnowrap' => false, ); } /** * Override the parent to generate help messages for all available query modules. + * @return string */ public function makeHelpMsg() { @@ -450,10 +510,13 @@ class ApiQuery extends ApiBase { /** * For all modules in $moduleList, generate help messages and join them together + * @param $moduleList array(modulename => classname) + * @param $paramName string Parameter name + * @return string */ private function makeHelpMsgHelper($moduleList, $paramName) { - $moduleDscriptions = array (); + $moduleDescriptions = array (); foreach ($moduleList as $moduleName => $moduleClass) { $module = new $moduleClass ($this, $moduleName, null); @@ -466,14 +529,15 @@ class ApiQuery extends ApiBase { $this->mAllowedGenerators[] = $moduleName; $msg .= "Generator:\n This module may be used as a generator\n"; } - $moduleDscriptions[] = $msg; + $moduleDescriptions[] = $msg; } - return implode("\n", $moduleDscriptions); + return implode("\n", $moduleDescriptions); } /** * Override to add extra parameters from PageSet + * @return string */ public function makeHelpMsgParameters() { $psModule = new ApiPageSet($this); @@ -489,31 +553,35 @@ class ApiQuery extends ApiBase { 'prop' => 'Which properties to get for the titles/revisions/pageids', 'list' => 'Which lists to get', 'meta' => 'Which meta data to get about the site', - 'generator' => 'Use the output of a list as the input for other prop/list/meta items', + 'generator' => array('Use the output of a list as the input for other prop/list/meta items', + 'NOTE: generator parameter names must be prefixed with a \'g\', see examples.'), 'redirects' => 'Automatically resolve redirects', - 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.' + 'indexpageids' => 'Include an additional pageids section listing all returned page IDs.', + 'export' => 'Export the current revisions of all given or generated pages', + 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export', ); } public function getDescription() { return array ( 'Query API module allows applications to get needed pieces of data from the MediaWiki databases,', - 'and is loosely based on the Query API interface currently available on all MediaWiki servers.', + 'and is loosely based on the old query.php interface.', 'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.' ); } protected function getExamples() { return array ( - 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment' + 'api.php?action=query&prop=revisions&meta=siteinfo&titles=Main%20Page&rvprop=user|comment', + 'api.php?action=query&generator=allpages&gapprefix=API/&prop=revisions', ); } public function getVersion() { $psModule = new ApiPageSet($this); $vers = array (); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 42548 2008-10-25 14:04:43Z tstarling $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 48629 2009-03-20 11:40:54Z catrope $'; $vers[] = $psModule->getVersion(); return $vers; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php index e6287eea..e835c042 100644 --- a/includes/api/ApiQueryAllCategories.php +++ b/includes/api/ApiQueryAllCategories.php @@ -103,21 +103,25 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { $item = array(); $result->setContent( $item, $titleObj->getText() ); if( isset( $prop['size'] ) ) { - $item['size'] = $row->cat_pages; + $item['size'] = intval($row->cat_pages); $item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files; - $item['files'] = $row->cat_files; - $item['subcats'] = $row->cat_subcats; + $item['files'] = intval($row->cat_files); + $item['subcats'] = intval($row->cat_subcats); } if( isset( $prop['hidden'] ) && $row->cat_hidden ) $item['hidden'] = ''; - $categories[] = $item; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $item); + if(!$fit) + { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->cat_title)); + break; + } } } $db->freeResult($res); if (is_null($resultPageSet)) { - $result->setIndexedTagName($categories, 'c'); - $result->addValue('query', $this->getModuleName(), $categories); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'c'); } else { $resultPageSet->populateFromTitles($pages); } @@ -171,6 +175,6 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllCategories.php 44590 2008-12-14 20:24:23Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllCategories.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index 9ad34aa2..7ae24665 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -101,8 +101,9 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $pageids = array (); $count = 0; + $result = $this->getResult(); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -120,10 +121,17 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $vals['fromid'] = intval($row->pl_from); if ($fld_title) { $title = Title :: makeTitle($params['namespace'], $row->pl_title); - $vals['ns'] = intval($title->getNamespace()); - $vals['title'] = $title->getPrefixedText(); + ApiQueryBase::addTitleInfo($vals, $title); + } + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + if($params['unique']) + $this->setContinueEnumParameter('from', $this->keyToTitle($row->pl_title)); + else + $this->setContinueEnumParameter('continue', $this->keyToTitle($row->pl_title) . "|" . $row->pl_from); + break; } - $data[] = $vals; } else { $pageids[] = $row->pl_from; } @@ -131,9 +139,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'l'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'l'); } else { $resultPageSet->populateFromPageIDs($pageids); } @@ -190,6 +196,6 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllLinks.php 45850 2009-01-17 20:03:25Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index 8395808b..5f9ff064 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -57,11 +57,11 @@ class ApiQueryAllUsers extends ApiQueryBase { $limit = $params['limit']; $this->addTables('user', 'u1'); - if( !is_null( $params['from'] ) ) - $this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) ); + if (!is_null($params['from'])) + $this->addWhere('u1.user_name >= ' . $db->addQuotes($this->keyToTitle($params['from']))); - if( isset( $params['prefix'] ) ) - $this->addWhere( 'u1.user_name LIKE "' . $db->escapeLike( $this->keyToTitle( $params['prefix'] ) ) . '%"' ); + if (!is_null($params['prefix'])) + $this->addWhere('u1.user_name LIKE "' . $db->escapeLike($this->keyToTitle( $params['prefix'])) . '%"'); if (!is_null($params['group'])) { // Filter only users that belong to a given group @@ -70,6 +70,9 @@ class ApiQueryAllUsers extends ApiQueryBase { $this->addWhereFld('ug1.ug_group', $params['group']); } + if ($params['witheditsonly']) + $this->addWhere('user_editcount > 0'); + if ($fld_groups) { // Show the groups the given users belong to // request more than needed to avoid not getting all rows that belong to one user @@ -124,7 +127,16 @@ class ApiQueryAllUsers extends ApiQueryBase { if (!$row || $lastUser !== $row->user_name) { // Save the last pass's user data if (is_array($lastUserData)) - $data[] = $lastUserData; + { + $fit = $result->addValue(array('query', $this->getModuleName()), + null, $lastUserData); + if(!$fit) + { + $this->setContinueEnumParameter('from', + $this->keyToTitle($lastUserData['name'])); + break; + } + } // No more rows left if (!$row) @@ -166,8 +178,7 @@ class ApiQueryAllUsers extends ApiQueryBase { $db->freeResult($res); - $result->setIndexedTagName($data, 'u'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'u'); } public function getAllowedParams() { @@ -192,7 +203,8 @@ class ApiQueryAllUsers extends ApiQueryBase { ApiBase :: PARAM_MIN => 1, ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 - ) + ), + 'witheditsonly' => false, ); } @@ -205,6 +217,7 @@ class ApiQueryAllUsers extends ApiQueryBase { 'What pieces of information to include.', '`groups` property uses more server resources and may return fewer results than the limit.'), 'limit' => 'How many total user names to return.', + 'witheditsonly' => 'Only list users who have made edits', ); } @@ -219,6 +232,6 @@ class ApiQueryAllUsers extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllUsers.php 44472 2008-12-11 21:51:01Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllUsers.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php index ea83c667..76d5d238 100644 --- a/includes/api/ApiQueryAllimages.php +++ b/includes/api/ApiQueryAllimages.php @@ -97,7 +97,7 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $titles = array(); $count = 0; $result = $this->getResult(); while ($row = $db->fetchObject($res)) { @@ -110,20 +110,23 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $file = $repo->newFileFromRow( $row ); - $data[] = array_merge(array('name' => $row->img_name), + $info = array_merge(array('name' => $row->img_name), ApiQueryImageInfo::getInfo($file, $prop, $result)); + $fit = $result->addValue(array('query', $this->getModuleName()), null, $info); + if( !$fit ) { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->img_name)); + break; + } } else { - $data[] = Title::makeTitle(NS_FILE, $row->img_name); + $titles[] = Title::makeTitle(NS_IMAGE, $row->img_name); } } $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'img'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'img'); } else { - $resultPageSet->populateFromTitles( $data ); + $resultPageSet->populateFromTitles($titles); } } @@ -202,6 +205,6 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllimages.php 44121 2008-12-01 17:14:30Z vyznev $'; + return __CLASS__ . ': $Id: ApiQueryAllimages.php 46845 2009-02-05 14:30:59Z catrope $'; } } diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php index 06683379..b19dc8fb 100644 --- a/includes/api/ApiQueryAllmessages.php +++ b/includes/api/ApiQueryAllmessages.php @@ -74,8 +74,13 @@ class ApiQueryAllmessages extends ApiQueryBase { //Get all requested messages $messages = array(); + $skip = !is_null($params['from']); foreach( $messages_target as $message ) { - $messages[$message] = wfMsg( $message ); + // Skip all messages up to $params['from'] + if($skip && $message === $params['from']) + $skip = false; + if(!$skip) + $messages[$message] = wfMsg( $message ); } //Print the result @@ -89,10 +94,14 @@ class ApiQueryAllmessages extends ApiQueryBase { } else { $result->setContent( $message, $value ); } - $messages_out[] = $message; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $message); + if(!$fit) + { + $this->setContinueEnumParameter('from', $name); + break; + } } - $result->setIndexedTagName( $messages_out, 'message' ); - $result->addValue( 'query', $this->getModuleName(), $messages_out ); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'message'); } public function getAllowedParams() { @@ -102,6 +111,7 @@ class ApiQueryAllmessages extends ApiQueryBase { ), 'filter' => array(), 'lang' => null, + 'from' => null, ); } @@ -110,6 +120,7 @@ class ApiQueryAllmessages extends ApiQueryBase { 'messages' => 'Which messages to output. "*" means all messages', 'filter' => 'Return only messages that contain this string', 'lang' => 'Return messages in this language', + 'from' => 'Return messages starting at this message', ); } @@ -125,6 +136,6 @@ class ApiQueryAllmessages extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllmessages.php 37504 2008-07-10 14:28:09Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllmessages.php 47048 2009-02-09 19:24:28Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 531fa02a..3d30aba9 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -135,8 +135,8 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->addOption('LIMIT', $limit+1); $res = $this->select(__METHOD__); - $data = array (); $count = 0; + $result = $this->getResult(); while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -147,10 +147,16 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $data[] = array( + $vals = array( 'pageid' => intval($row->page_id), 'ns' => intval($title->getNamespace()), 'title' => $title->getPrefixedText()); + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('from', $this->keyToTitle($row->page_title)); + break; + } } else { $resultPageSet->processDbRow($row); } @@ -158,9 +164,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'p'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p'); } } @@ -264,6 +268,6 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 44863 2008-12-20 23:54:04Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index f67e0044..95972392 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -38,7 +38,9 @@ if (!defined('MEDIAWIKI')) { */ class ApiQueryBacklinks extends ApiQueryGeneratorBase { - private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID; + private $params, $rootTitle, $contRedirs, $contLevel, $contTitle, $contID, $redirID, $redirect; + private $bl_ns, $bl_from, $bl_table, $bl_code, $bl_title, $bl_sort, $bl_fields, $hasNS; + private $pageMap, $resultArr; // output element name, database column field prefix, database table private $backlinksSettings = array ( @@ -61,6 +63,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { public function __construct($query, $moduleName) { extract($this->backlinksSettings[$moduleName]); + $this->resultArr = array(); parent :: __construct($query, $moduleName, $code); $this->bl_ns = $prefix . '_namespace'; @@ -137,11 +140,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->addFields($this->bl_title); if($this->hasNS) $this->addFields($this->bl_ns); + // We can't use LinkBatch here because $this->hasNS may be false $titleWhere = array(); foreach($this->redirTitles as $t) - $titleWhere[] = "({$this->bl_title} = ".$db->addQuotes($t->getDBKey()). - ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : "") . - ")"; + $titleWhere[] = "{$this->bl_title} = ".$db->addQuotes($t->getDBKey()). + ($this->hasNS ? " AND {$this->bl_ns} = '{$t->getNamespace()}'" : ""); $this->addWhere($db->makeList($titleWhere, LIST_OR)); $this->addWhereFld('page_namespace', $this->params['namespace']); if(!is_null($this->redirID)) @@ -168,6 +171,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->addWhereFld('page_is_redirect', 0); $this->addOption('LIMIT', $this->params['limit'] + 1); $this->addOption('ORDER BY', $this->bl_sort); + $this->addOption('USE INDEX', array('page' => 'PRIMARY')); } private function run($resultPageSet = null) { @@ -187,7 +191,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__.'::firstQuery'); $count = 0; - $this->data = array (); + $this->pageMap = array(); // Maps ns and title to pageid $this->continueStr = null; $this->redirTitles = array(); while ($row = $db->fetchObject($res)) { @@ -202,6 +206,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->extractRowInfo($row); else { + $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; if($row->page_is_redirect) $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title); $resultPageSet->processDbRow($row); @@ -222,10 +227,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { // We've reached the one extra which shows that there are additional pages to be had. Stop here... // We need to keep the parent page of this redir in if($this->hasNS) - $contTitle = Title::makeTitle($row->{$this->bl_ns}, $row->{$this->bl_title}); + $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}]; else - $contTitle = Title::makeTitle(NS_FILE, $row->{$this->bl_title}); - $this->continueStr = $this->getContinueRedirStr($contTitle->getArticleID(), $row->page_id); + $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}]; + $this->continueStr = $this->getContinueRedirStr($parentID, $row->page_id); break; } @@ -236,41 +241,80 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } $db->freeResult($res); } - if(!is_null($this->continueStr)) - $this->setContinueEnumParameter('continue', $this->continueStr); - if (is_null($resultPageSet)) { - $resultData = array(); - foreach($this->data as $ns => $a) - foreach($a as $title => $arr) - $resultData[] = $arr; - $result = $this->getResult(); - $result->setIndexedTagName($resultData, $this->bl_code); - $result->addValue('query', $this->getModuleName(), $resultData); + // Try to add the result data in one go and pray that it fits + $fit = $this->getResult()->addValue('query', $this->getModuleName(), array_values($this->resultArr)); + if(!$fit) + { + // It didn't fit. Add elements one by one until the + // result is full. + foreach($this->resultArr as $pageID => $arr) + { + // Add the basic entry without redirlinks first + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName()), + null, array_diff_key($arr, array('redirlinks' => ''))); + if(!$fit) + { + $this->continueStr = $this->getContinueStr($pageID); + break; + } + + $hasRedirs = false; + foreach((array)@$arr['redirlinks'] as $key => $redir) + { + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName(), $pageID, 'redirlinks'), + $key, $redir); + if(!$fit) + { + $this->continueStr = $this->getContinueRedirStr($pageID, $redir['pageid']); + break; + } + $hasRedirs = true; + } + if($hasRedirs) + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName(), $pageID, 'redirlinks'), + $this->bl_code); + if(!$fit) + break; + } + } + + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName()), + $this->bl_code); } + if(!is_null($this->continueStr)) + $this->setContinueEnumParameter('continue', $this->continueStr); } private function extractRowInfo($row) { - if(!isset($this->data[$row->page_namespace][$row->page_title])) { - $this->data[$row->page_namespace][$row->page_title]['pageid'] = $row->page_id; - ApiQueryBase::addTitleInfo($this->data[$row->page_namespace][$row->page_title], Title::makeTitle($row->page_namespace, $row->page_title)); - if($row->page_is_redirect) - { - $this->data[$row->page_namespace][$row->page_title]['redirect'] = ''; - $this->redirTitles[] = Title::makeTitle($row->page_namespace, $row->page_title); - } + $this->pageMap[$row->page_namespace][$row->page_title] = $row->page_id; + $t = Title::makeTitle($row->page_namespace, $row->page_title); + $a = array('pageid' => intval($row->page_id)); + ApiQueryBase::addTitleInfo($a, $t); + if($row->page_is_redirect) + { + $a['redirect'] = ''; + $this->redirTitles[] = $t; } + // Put all the results in an array first + $this->resultArr[$a['pageid']] = $a; } private function extractRedirRowInfo($row) { - $a['pageid'] = $row->page_id; + $a['pageid'] = intval($row->page_id); ApiQueryBase::addTitleInfo($a, Title::makeTitle($row->page_namespace, $row->page_title)); if($row->page_is_redirect) $a['redirect'] = ''; $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; - $this->data[$ns][$row->{$this->bl_title}]['redirlinks'][] = $a; - $this->getResult()->setIndexedTagName($this->data[$ns][$row->{$this->bl_title}]['redirlinks'], $this->bl_code); + $parentID = $this->pageMap[$ns][$row->{$this->bl_title}]; + // Put all the results in an array first + $this->resultArr[$parentID]['redirlinks'][] = $a; + $this->getResult()->setIndexedTagName($this->resultArr[$parentID]['redirlinks'], $this->bl_code); } protected function processContinue() { @@ -404,8 +448,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { "api.php?action=query&generator=embeddedin&geititle=Template:Stub&prop=info" ), 'imageusage' => array ( - "api.php?action=query&list=imageusage&iutitle=Image:Albert%20Einstein%20Head.jpg", - "api.php?action=query&generator=imageusage&giutitle=Image:Albert%20Einstein%20Head.jpg&prop=info" + "api.php?action=query&list=imageusage&iutitle=File:Albert%20Einstein%20Head.jpg", + "api.php?action=query&generator=imageusage&giutitle=File:Albert%20Einstein%20Head.jpg&prop=info" ) ); @@ -413,6 +457,6 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 46135 2009-01-24 13:03:40Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 50217 2009-05-05 13:12:16Z tstarling $'; } } diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 896dd00c..9d1cbcea 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -30,7 +30,8 @@ if (!defined('MEDIAWIKI')) { /** * This is a base class for all Query modules. - * It provides some common functionality such as constructing various SQL queries. + * It provides some common functionality such as constructing various SQL + * queries. * * @ingroup API */ @@ -58,8 +59,9 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a set of tables to the internal array - * @param mixed $tables Table name or array of table names - * @param mixed $alias Table alias, or null for no alias. Cannot be used with multiple tables + * @param $tables mixed Table name or array of table names + * @param $alias mixed Table alias, or null for no alias. Cannot be + * used with multiple tables */ protected function addTables($tables, $alias = null) { if (is_array($tables)) { @@ -75,8 +77,8 @@ abstract class ApiQueryBase extends ApiBase { /** * Get the SQL for a table name with alias - * @param string $table Table name - * @param string $alias Alias + * @param $table string Table name + * @param $alias string Alias * @return string SQL */ protected function getAliasedName($table, $alias) { @@ -86,9 +88,11 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a set of JOIN conditions to the internal array * - * JOIN conditions are formatted as array( tablename => array(jointype, conditions) - * e.g. array('page' => array('LEFT JOIN', 'page_id=rev_page')) - * @param array $join_conds JOIN conditions + * JOIN conditions are formatted as array( tablename => array(jointype, + * conditions) e.g. array('page' => array('LEFT JOIN', + * 'page_id=rev_page')) . conditions may be a string or an + * addWhere()-style array + * @param $join_conds array JOIN conditions */ protected function addJoinConds($join_conds) { if(!is_array($join_conds)) @@ -98,7 +102,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a set of fields to select to the internal array - * @param mixed $value Field name or array of field names + * @param $value mixed Field name or array of field names */ protected function addFields($value) { if (is_array($value)) @@ -109,8 +113,8 @@ abstract class ApiQueryBase extends ApiBase { /** * Same as addFields(), but add the fields only if a condition is met - * @param mixed $value See addFields() - * @param bool $condition If false, do nothing + * @param $value mixed See addFields() + * @param $condition bool If false, do nothing * @return bool $condition */ protected function addFieldsIf($value, $condition) { @@ -130,7 +134,7 @@ abstract class ApiQueryBase extends ApiBase { * * For example, array('foo=bar', 'baz' => 3, 'bla' => 'foo') translates * to "foo=bar AND baz='3' AND bla='foo'" - * @param mixed $value String or array + * @param $value mixed String or array */ protected function addWhere($value) { if (is_array($value)) { @@ -145,8 +149,8 @@ abstract class ApiQueryBase extends ApiBase { /** * Same as addWhere(), but add the WHERE clauses only if a condition is met - * @param mixed $value See addWhere() - * @param bool $condition If false, do nothing + * @param $value mixed See addWhere() + * @param $condition boolIf false, do nothing * @return bool $condition */ protected function addWhereIf($value, $condition) { @@ -159,8 +163,8 @@ abstract class ApiQueryBase extends ApiBase { /** * Equivalent to addWhere(array($field => $value)) - * @param string $field Field name - * @param string $value Value; ignored if null or empty array; + * @param $field string Field name + * @param $value string Value; ignored if null or empty array; */ protected function addWhereFld($field, $value) { // Use count() to its full documented capabilities to simultaneously @@ -172,12 +176,16 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a WHERE clause corresponding to a range, and an ORDER BY * clause to sort in the right direction - * @param string $field Field name - * @param string $dir If 'newer', sort in ascending order, otherwise sort in descending order - * @param string $start Value to start the list at. If $dir == 'newer' this is the lower boundary, otherwise it's the upper boundary - * @param string $end Value to end the list at. If $dir == 'newer' this is the upper boundary, otherwise it's the lower boundary + * @param $field string Field name + * @param $dir string If 'newer', sort in ascending order, otherwise + * sort in descending order + * @param $start string Value to start the list at. If $dir == 'newer' + * this is the lower boundary, otherwise it's the upper boundary + * @param $end string Value to end the list at. If $dir == 'newer' this + * is the upper boundary, otherwise it's the lower boundary + * @param $sort bool If false, don't add an ORDER BY clause */ - protected function addWhereRange($field, $dir, $start, $end) { + protected function addWhereRange($field, $dir, $start, $end, $sort = true) { $isDirNewer = ($dir === 'newer'); $after = ($isDirNewer ? '>=' : '<='); $before = ($isDirNewer ? '<=' : '>='); @@ -189,17 +197,20 @@ abstract class ApiQueryBase extends ApiBase { if (!is_null($end)) $this->addWhere($field . $before . $db->addQuotes($end)); - $order = $field . ($isDirNewer ? '' : ' DESC'); - if (!isset($this->options['ORDER BY'])) - $this->addOption('ORDER BY', $order); - else - $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order); + if ($sort) { + $order = $field . ($isDirNewer ? '' : ' DESC'); + if (!isset($this->options['ORDER BY'])) + $this->addOption('ORDER BY', $order); + else + $this->addOption('ORDER BY', $this->options['ORDER BY'] . ', ' . $order); + } } /** - * Add an option such as LIMIT or USE INDEX - * @param string $name Option name - * @param string $value Option value + * Add an option such as LIMIT or USE INDEX. If an option was set + * before, the old value will be overwritten + * @param $name string Option name + * @param $value string Option value */ protected function addOption($name, $value = null) { if (is_null($value)) @@ -210,7 +221,8 @@ abstract class ApiQueryBase extends ApiBase { /** * Execute a SELECT query based on the values in the internal arrays - * @param string $method Function the query should be attributed to. You should usually use __METHOD__ here + * @param $method string Function the query should be attributed to. + * You should usually use __METHOD__ here * @return ResultWrapper */ protected function select($method) { @@ -243,10 +255,11 @@ abstract class ApiQueryBase extends ApiBase { } /** - * Add information (title and namespace) about a Title object to a result array - * @param array $arr Result array à la ApiResult - * @param Title $title Title object - * @param string $prefix Module prefix + * Add information (title and namespace) about a Title object to a + * result array + * @param $arr array Result array à la ApiResult + * @param $title Title + * @param $prefix string Module prefix */ public static function addTitleInfo(&$arr, $title, $prefix='') { $arr[$prefix . 'ns'] = intval($title->getNamespace()); @@ -256,7 +269,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Override this method to request extra fields from the pageSet * using $pageSet->requestField('fieldName') - * @param ApiPageSet $pageSet + * @param $pageSet ApiPageSet */ public function requestExtraData($pageSet) { } @@ -271,31 +284,54 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a sub-element under the page element with the given page ID - * @param int $pageId Page ID - * @param array $data Data array à la ApiResult + * @param $pageId int Page ID + * @param $data array Data array à la ApiResult + * @return bool Whether the element fit in the result */ protected function addPageSubItems($pageId, $data) { $result = $this->getResult(); $result->setIndexedTagName($data, $this->getModulePrefix()); - $result->addValue(array ('query', 'pages', intval($pageId)), + return $result->addValue(array('query', 'pages', intval($pageId)), $this->getModuleName(), $data); } + + /** + * Same as addPageSubItems(), but one element of $data at a time + * @param $pageId int Page ID + * @param $data array Data array à la ApiResult + * @param $elemname string XML element name. If null, getModuleName() + * is used + * @return bool Whether the element fit in the result + */ + protected function addPageSubItem($pageId, $item, $elemname = null) { + if(is_null($elemname)) + $elemname = $this->getModulePrefix(); + $result = $this->getResult(); + $fit = $result->addValue(array('query', 'pages', $pageId, + $this->getModuleName()), null, $item); + if(!$fit) + return false; + $result->setIndexedTagName_internal(array('query', 'pages', $pageId, + $this->getModuleName()), $elemname); + return true; + } /** * Set a query-continue value - * @param $paramName Parameter name - * @param $paramValue Parameter value + * @param $paramName string Parameter name + * @param $paramValue string Parameter value */ protected function setContinueEnumParameter($paramName, $paramValue) { - $paramName = $this->encodeParamName($paramName); $msg = array( $paramName => $paramValue ); + $this->getResult()->disableSizeCheck(); $this->getResult()->addValue('query-continue', $this->getModuleName(), $msg); + $this->getResult()->enableSizeCheck(); } /** - * Get the Query database connection (readonly) + * Get the Query database connection (read-only) * @return Database */ protected function getDB() { @@ -306,12 +342,10 @@ abstract class ApiQueryBase extends ApiBase { /** * Selects the query database connection with the given name. - * If no such connection has been requested before, it will be created. - * Subsequent calls with the same $name will return the same connection - * as the first, regardless of $db or $groups new values. - * @param string $name Name to assign to the database connection - * @param int $db One of the DB_* constants - * @param array $groups Query groups + * See ApiQuery::getNamedDB() for more information + * @param $name string Name to assign to the database connection + * @param $db int One of the DB_* constants + * @param $groups array Query groups * @return Database */ public function selectNamedDB($name, $db, $groups) { @@ -328,7 +362,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Convert a title to a DB key - * @param string $title Page title with spaces + * @param $title string Page title with spaces * @return string Page title with underscores */ public function titleToKey($title) { @@ -343,7 +377,7 @@ abstract class ApiQueryBase extends ApiBase { /** * The inverse of titleToKey() - * @param string $key Page title with underscores + * @param $key string Page title with underscores * @return string Page title with spaces */ public function keyToTitle($key) { @@ -359,7 +393,7 @@ abstract class ApiQueryBase extends ApiBase { /** * An alternative to titleToKey() that doesn't trim trailing spaces - * @param string $titlePart Title part with spaces + * @param $titlePart string Title part with spaces * @return string Title part with underscores */ public function titlePartToKey($titlePart) { @@ -368,7 +402,7 @@ abstract class ApiQueryBase extends ApiBase { /** * An alternative to keyToTitle() that doesn't trim trailing spaces - * @param string $keyPart Key part with spaces + * @param $keyPart string Key part with spaces * @return string Key part with underscores */ public function keyPartToTitle($keyPart) { @@ -380,7 +414,7 @@ abstract class ApiQueryBase extends ApiBase { * @return string */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 44461 2008-12-11 19:11:11Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 47450 2009-02-18 15:26:09Z catrope $'; } } @@ -406,6 +440,8 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { /** * Overrides base class to prepend 'g' to every generator parameter + * @param $paramNames string Parameter name + * @return string Prefixed parameter name */ public function encodeParamName($paramName) { if ($this->mIsGenerator) @@ -416,7 +452,8 @@ abstract class ApiQueryGeneratorBase extends ApiQueryBase { /** * Execute this module as a generator - * @param $resultPageSet PageSet: All output should be appended to this object + * @param $resultPageSet ApiPageSet: All output should be appended to + * this object */ public abstract function executeGenerator($resultPageSet); } diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index 6f356cea..c5ffc37b 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -115,7 +115,7 @@ class ApiQueryBlocks extends ApiQueryBase { "ipb_range_end >= '$upper'" )); } - if(!$wgUser->isAllowed('suppress')) + if(!$wgUser->isAllowed('hideuser')) $this->addWhereFld('ipb_deleted', 0); // Purge expired entries on one in every 10 queries @@ -169,10 +169,14 @@ class ApiQueryBlocks extends ApiQueryBase { if($row->ipb_allow_usertalk) $block['allowusertalk'] = ''; } - $data[] = $block; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $block); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ipb_timestamp)); + break; + } } - $result->setIndexedTagName($data, 'block'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'block'); } protected function prepareUsername($user) @@ -259,6 +263,6 @@ class ApiQueryBlocks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBlocks.php 43676 2008-11-18 15:11:11Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryBlocks.php 48213 2009-03-09 10:01:00Z aaron $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index 9c4e9b41..f91a04e5 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -81,6 +81,19 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { $this->addTables('categorylinks'); $this->addWhereFld('cl_from', array_keys($this->getPageSet()->getGoodTitles())); + if(!is_null($params['categories'])) + { + $cats = array(); + foreach($params['categories'] as $cat) + { + $title = Title::newFromText($cat); + if(!$title || $title->getNamespace() != NS_CATEGORY) + $this->setWarning("``$cat'' is not a category"); + else + $cats[] = $title->getDBkey(); + } + $this->addWhereFld('cl_to', $cats); + } if(!is_null($params['continue'])) { $cont = explode('|', $params['continue']); if(count($cont) != 2) @@ -112,6 +125,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { $this->addWhere(array('pp_propname IS NULL')); } + $this->addOption('USE INDEX', array('categorylinks' => 'cl_from')); # Don't order by cl_from if it's constant in the WHERE clause if(count($this->getPageSet()->getGoodTitles()) == 1) $this->addOption('ORDER BY', 'cl_to'); @@ -123,8 +137,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -134,16 +146,8 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { '|' . $this->keyToTitle($row->cl_to)); break; } - if ($lastId != $row->cl_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->cl_from; - } $title = Title :: makeTitle(NS_CATEGORY, $row->cl_to); - $vals = array(); ApiQueryBase :: addTitleInfo($vals, $title); if ($fld_sortkey) @@ -151,13 +155,14 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { if ($fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->cl_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->cl_from . + '|' . $this->keyToTitle($row->cl_to)); + break; + } } - } else { $titles = array(); @@ -202,6 +207,9 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 ), 'continue' => null, + 'categories' => array( + ApiBase :: PARAM_ISMULTI => true, + ), ); } @@ -211,6 +219,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { 'limit' => 'How many categories to return', 'show' => 'Which kind of categories to show', 'continue' => 'When more results are available, use this to continue', + 'categories' => 'Only list these categories. Useful for checking whether a certain page is in a certain category', ); } @@ -228,6 +237,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategories.php 44585 2008-12-14 17:39:50Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategories.php 50097 2009-05-01 06:35:57Z tstarling $'; } } diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php index f83c4a5b..49e4554e 100644 --- a/includes/api/ApiQueryCategoryInfo.php +++ b/includes/api/ApiQueryCategoryInfo.php @@ -29,7 +29,7 @@ if (!defined('MEDIAWIKI')) { } /** - * This query adds <categories> subelement to all pages with the list of images embedded into those pages. + * This query adds the <categories> subelement to all pages with the list of categories the page is in * * @ingroup API */ @@ -39,7 +39,8 @@ class ApiQueryCategoryInfo extends ApiQueryBase { parent :: __construct($query, $moduleName, 'ci'); } - public function execute() { + public function execute() { + $params = $this->extractRequestParams(); $alltitles = $this->getPageSet()->getAllTitlesByNamespace(); if ( empty( $alltitles[NS_CATEGORY] ) ) { return; @@ -65,27 +66,49 @@ class ApiQueryCategoryInfo extends ApiQueryBase { 'pp_propname' => 'hiddencat')), )); $this->addFields(array('cat_title', 'cat_pages', 'cat_subcats', 'cat_files', 'pp_propname AS cat_hidden')); - $this->addWhere(array('cat_title' => $cattitles)); + $this->addWhere(array('cat_title' => $cattitles)); + if(!is_null($params['continue'])) + { + $title = $this->getDB()->addQuotes($params['continue']); + $this->addWhere("cat_title >= $title"); + } + $this->addOption('ORDER BY', 'cat_title'); $db = $this->getDB(); $res = $this->select(__METHOD__); - $data = array(); $catids = array_flip($cattitles); while($row = $db->fetchObject($res)) { $vals = array(); - $vals['size'] = $row->cat_pages; + $vals['size'] = intval($row->cat_pages); $vals['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files; - $vals['files'] = $row->cat_files; - $vals['subcats'] = $row->cat_subcats; + $vals['files'] = intval($row->cat_files); + $vals['subcats'] = intval($row->cat_subcats); if($row->cat_hidden) $vals['hidden'] = ''; - $this->addPageSubItems($catids[$row->cat_title], $vals); + $fit = $this->addPageSubItems($catids[$row->cat_title], $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->cat_title); + break; + } } $db->freeResult($res); } + public function getAllowedParams() { + return array ( + 'continue' => null, + ); + } + + public function getParamDescription() { + return array ( + 'continue' => 'When more results are available, use this to continue', + ); + } + public function getDescription() { return 'Returns information about the given categories'; } @@ -95,6 +118,6 @@ class ApiQueryCategoryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 44590 2008-12-14 20:24:23Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategoryInfo.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php index e2f577a2..dc5a8265 100644 --- a/includes/api/ApiQueryCategoryMembers.php +++ b/includes/api/ApiQueryCategoryMembers.php @@ -112,31 +112,38 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { break; } - $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys - if (is_null($resultPageSet)) { $vals = array(); if ($fld_ids) $vals['pageid'] = intval($row->page_id); if ($fld_title) { $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $vals['ns'] = intval($title->getNamespace()); - $vals['title'] = $title->getPrefixedText(); + ApiQueryBase::addTitleInfo($vals, $title); } if ($fld_sortkey) $vals['sortkey'] = $row->cl_sortkey; if ($fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->cl_timestamp); - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), + null, $vals); + if(!$fit) + { + if ($params['sort'] == 'timestamp') + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->cl_timestamp)); + else + $this->setContinueEnumParameter('continue', $this->getContinueStr($row, $lastSortKey)); + break; + } } else { $resultPageSet->processDbRow($row); } + $lastSortKey = $row->cl_sortkey; // detect duplicate sortkeys } $db->freeResult($res); if (is_null($resultPageSet)) { - $this->getResult()->setIndexedTagName($data, 'cm'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal( + array('query', $this->getModuleName()), 'cm'); } } @@ -255,6 +262,6 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 42197 2008-10-18 10:09:19Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 408421c4..bdbe05e8 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -56,13 +56,27 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $fld_len = isset($prop['len']); $fld_content = isset($prop['content']); $fld_token = isset($prop['token']); - + $result = $this->getResult(); $pageSet = $this->getPageSet(); $titles = $pageSet->getTitles(); $data = array(); + + // This module operates in three modes: + // 'revs': List deleted revs for certain titles + // 'user': List deleted revs by a certain user + // 'all': List all deleted revs + $mode = 'all'; + if(count($titles) > 0) + $mode = 'revs'; + else if(!is_null($params['user'])) + $mode = 'user'; + + if(!is_null($params['user']) && !is_null($params['excludeuser'])) + $this->dieUsage('user and excludeuser cannot be used together', 'badparams'); $this->addTables('archive'); + $this->addWhere('ar_deleted = 0'); $this->addFields(array('ar_title', 'ar_namespace', 'ar_timestamp')); if($fld_revid) $this->addFields('ar_rev_id'); @@ -102,34 +116,88 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $token = $wgUser->editToken(); // We need a custom WHERE clause that matches all titles. - if(count($titles) > 0) + if($mode == 'revs') { $lb = new LinkBatch($titles); $where = $lb->constructSet('ar', $db); $this->addWhere($where); - } else { - $this->dieUsage('You have to specify a page title or titles'); + } + elseif($mode == 'all') + { + $this->addWhereFld('ar_namespace', $params['namespace']); + if(!is_null($params['from'])) + { + $from = $this->getDB()->strencode($this->titleToKey($params['from'])); + $this->addWhere("ar_title >= '$from'"); + } + } + + if(!is_null($params['user'])) { + $this->addWhereFld('ar_user_text', $params['user']); + } elseif(!is_null($params['excludeuser'])) { + $this->addWhere('ar_user_text != ' . + $this->getDB()->addQuotes($params['excludeuser'])); + } + + if(!is_null($params['continue']) && ($mode == 'all' || $mode == 'revs')) + { + $cont = explode('|', $params['continue']); + if(count($cont) != 3) + $this->dieUsage("Invalid continue param. You should pass the original value returned by the previous query", "badcontinue"); + $ns = intval($cont[0]); + $title = $this->getDB()->strencode($this->titleToKey($cont[1])); + $ts = $this->getDB()->strencode($cont[2]); + $op = ($params['dir'] == 'newer' ? '>' : '<'); + $this->addWhere("ar_namespace $op $ns OR " . + "(ar_namespace = $ns AND " . + "(ar_title $op '$title' OR " . + "(ar_title = '$title' AND " . + "ar_timestamp = '$ts')))"); } $this->addOption('LIMIT', $limit + 1); - $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); + $this->addOption('USE INDEX', array('archive' => ($mode == 'user' ? 'usertext_timestamp' : 'name_title_timestamp'))); + if($mode == 'all') + { + if($params['unique']) + { + $this->addOption('GROUP BY', 'ar_title'); + $this->addOption('ORDER BY', 'ar_title'); + } + else + $this->addOption('ORDER BY', 'ar_title, ar_timestamp'); + } + else + { + if($mode == 'revs') + { + // Sort by ns and title in the same order as timestamp for efficiency + $this->addWhereRange('ar_namespace', $params['dir'], null, null); + $this->addWhereRange('ar_title', $params['dir'], null, null); + } + $this->addWhereRange('ar_timestamp', $params['dir'], $params['start'], $params['end']); + } $res = $this->select(__METHOD__); - $pages = array(); + $pageMap = array(); // Maps ns&title to (fake) pageid $count = 0; - // First populate the $pages array + $newPageID = 0; while($row = $db->fetchObject($res)) { if(++$count > $limit) { // We've had enough - $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); + if($mode == 'all' || $mode == 'revs') + $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . + $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp); + else + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); break; } $rev = array(); $rev['timestamp'] = wfTimestamp(TS_ISO_8601, $row->ar_timestamp); if($fld_revid) - $rev['revid'] = $row->ar_rev_id; + $rev['revid'] = intval($row->ar_rev_id); if($fld_user) $rev['user'] = $row->ar_user_text; if($fld_comment) @@ -142,31 +210,38 @@ class ApiQueryDeletedrevs extends ApiQueryBase { if($fld_content) ApiResult::setContent($rev, Revision::getRevisionText($row)); - $t = Title::makeTitle($row->ar_namespace, $row->ar_title); - if(!isset($pages[$t->getPrefixedText()])) + if(!isset($pageMap[$row->ar_namespace][$row->ar_title])) { - $pages[$t->getPrefixedText()] = array( - 'title' => $t->getPrefixedText(), - 'ns' => intval($row->ar_namespace), - 'revisions' => array($rev) - ); + $pageID = $newPageID++; + $pageMap[$row->ar_namespace][$row->ar_title] = $pageID; + $t = Title::makeTitle($row->ar_namespace, $row->ar_title); + $a['revisions'] = array($rev); + $result->setIndexedTagName($a['revisions'], 'rev'); + ApiQueryBase::addTitleInfo($a, $t); if($fld_token) - $pages[$t->getPrefixedText()]['token'] = $token; + $a['token'] = $token; + $fit = $result->addValue(array('query', $this->getModuleName()), $pageID, $a); } else - $pages[$t->getPrefixedText()]['revisions'][] = $rev; + { + $pageID = $pageMap[$row->ar_namespace][$row->ar_title]; + $fit = $result->addValue( + array('query', $this->getModuleName(), $pageID, 'revisions'), + null, $rev); + } + if(!$fit) + { + if($mode == 'all' || $mode == 'revs') + $this->setContinueEnumParameter('continue', intval($row->ar_namespace) . '|' . + $this->keyToTitle($row->ar_title) . '|' . $row->ar_timestamp); + else + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->ar_timestamp)); + break; + } } $db->freeResult($res); - - // We don't want entire pagenames as keys, so let's make this array indexed - foreach($pages as $page) - { - $result->setIndexedTagName($page['revisions'], 'rev'); - $data[] = $page; - } - $result->setIndexedTagName($data, 'page'); - $result->addValue('query', $this->getModuleName(), $data); - } + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); + } public function getAllowedParams() { return array ( @@ -183,6 +258,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase { ), ApiBase :: PARAM_DFLT => 'older' ), + 'from' => null, + 'continue' => null, + 'unique' => false, + 'user' => array( + ApiBase :: PARAM_TYPE => 'user' + ), + 'excludeuser' => array( + ApiBase :: PARAM_TYPE => 'user' + ), + 'namespace' => array( + ApiBase :: PARAM_TYPE => 'namespace', + ApiBase :: PARAM_DFLT => 0, + ), 'limit' => array( ApiBase :: PARAM_DFLT => 10, ApiBase :: PARAM_TYPE => 'limit', @@ -202,34 +290,51 @@ class ApiQueryDeletedrevs extends ApiQueryBase { 'token' ), ApiBase :: PARAM_ISMULTI => true - ) + ), ); } public function getParamDescription() { return array ( - 'start' => 'The timestamp to start enumerating from', - 'end' => 'The timestamp to stop enumerating at', - 'dir' => 'The direction in which to enumerate', + 'start' => 'The timestamp to start enumerating from. (1,2)', + 'end' => 'The timestamp to stop enumerating at. (1,2)', + 'dir' => 'The direction in which to enumerate. (1,2)', 'limit' => 'The maximum amount of revisions to list', - 'prop' => 'Which properties to get' + 'prop' => 'Which properties to get', + 'namespace' => 'Only list pages in this namespace (3)', + 'user' => 'Only list revisions by this user', + 'excludeuser' => 'Don\'t list revisions by this user', + 'from' => 'Start listing at this title (3)', + 'continue' => 'When more results are available, use this to continue (3)', + 'unique' => 'List only one revision for each page (3)', ); } public function getDescription() { - return 'List deleted revisions.'; + return array( 'List deleted revisions.', + 'This module operates in three modes:', + '1) List deleted revisions for the given title(s), sorted by timestamp', + '2) List deleted contributions for the given user, sorted by timestamp (no titles specified)', + '3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, druser not set)', + 'Certain parameters only apply to some modes and are ignored in others.', + 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3.', + ); } protected function getExamples() { return array ( - 'List the first 50 deleted revisions', + 'List the last deleted revisions of Main Page and Talk:Main Page, with content (mode 1):', + ' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content', + 'List the last 50 deleted contributions by Bob (mode 2):', + ' api.php?action=query&list=deletedrevs&druser=Bob&drlimit=50', + 'List the first 50 deleted revisions in the main namespace (mode 3):', ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50', - 'List the last deleted revisions of Main Page and Talk:Main Page, with content:', - ' api.php?action=query&list=deletedrevs&titles=Main%20Page|Talk:Main%20Page&drprop=user|comment|content' + 'List the first 50 deleted pages in the Talk namespace (mode 3):', + ' api.php?action=query&list=deletedrevs&drdir=newer&drlimit=50&drnamespace=1&drunique', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 40798 2008-09-13 20:41:58Z aaron $'; + return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 50220 2009-05-05 14:07:59Z tstarling $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php index 5f7d7ee0..84a8a96d 100644 --- a/includes/api/ApiQueryDuplicateFiles.php +++ b/includes/api/ApiQueryDuplicateFiles.php @@ -86,9 +86,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); $db = $this->getDB(); $count = 0; - $data = array(); $titles = array(); - $lastName = ''; while($row = $db->fetchObject($res)) { if(++$count > $params['limit']) @@ -104,27 +102,23 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { $titles[] = Title::makeTitle(NS_FILE, $row->dup_name); else { - if($row->orig_name != $lastName) - { - if($lastName != '') - { - $this->addPageSubItems($images[$lastName], $data); - $data = array(); - } - $lastName = $row->orig_name; - } - - $data[] = array( + $r = array( 'name' => $row->dup_name, 'user' => $row->dup_user_text, 'timestamp' => wfTimestamp(TS_ISO_8601, $row->dup_timestamp) ); + $fit = $this->addPageSubItem($images[$row->orig_name], $r); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + $this->keyToTitle($row->orig_name) . '|' . + $this->keyToTitle($row->dup_name)); + break; + } } } if(!is_null($resultPageSet)) $resultPageSet->populateFromTitles($titles); - else if($lastName != '') - $this->addPageSubItems($images[$lastName], $data); $db->freeResult($res); } @@ -153,12 +147,12 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { } protected function getExamples() { - return array ( 'api.php?action=query&titles=Image:Albert_Einstein_Head.jpg&prop=duplicatefiles', + return array ( 'api.php?action=query&titles=File:Albert_Einstein_Head.jpg&prop=duplicatefiles', 'api.php?action=query&generator=allimages&prop=duplicatefiles', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 44121 2008-12-01 17:14:30Z vyznev $'; + return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 48215 2009-03-09 10:44:34Z catrope $'; } } diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index 85e21f42..0ba2767a 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -110,7 +110,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); - $data = array (); + $result = $this->getResult(); $count = 0; while ($row = $db->fetchObject($res)) { if (++ $count > $limit) { @@ -125,12 +125,16 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { $vals['pageid'] = intval($row->page_id); if ($fld_title) { $title = Title :: makeTitle($row->page_namespace, $row->page_title); - $vals['ns'] = intval($title->getNamespace()); - $vals['title'] = $title->getPrefixedText(); + ApiQueryBase::addTitleInfo($vals, $title); } if ($fld_url) $vals['url'] = $row->el_to; - $data[] = $vals; + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('offset', $offset + $count - 1); + break; + } } else { $resultPageSet->processDbRow($row); } @@ -138,9 +142,8 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, $this->getModulePrefix()); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), + $this->getModulePrefix()); } } @@ -206,6 +209,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 43271 2008-11-06 22:38:42Z siebrand $'; + return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index a24f15d8..7a91f432 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -61,8 +61,6 @@ class ApiQueryExternalLinks extends ApiQueryBase { $db = $this->getDB(); $res = $this->select(__METHOD__); - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -71,23 +69,15 @@ class ApiQueryExternalLinks extends ApiQueryBase { $this->setContinueEnumParameter('offset', @$params['offset'] + $params['limit']); break; } - if ($lastId != $row->el_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->el_from; - } - $entry = array(); ApiResult :: setContent($entry, $row->el_to); - $data[] = $entry; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->el_from, $entry); + if(!$fit) + { + $this->setContinueEnumParameter('offset', @$params['offset'] + $count - 1); + break; + } } - $db->freeResult($res); } @@ -123,6 +113,6 @@ class ApiQueryExternalLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 37270 2008-07-07 17:32:22Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 612d5cc9..7d880456 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -56,49 +56,116 @@ class ApiQueryImageInfo extends ApiQueryBase { } $pageIds = $this->getPageSet()->getAllTitlesByNamespace(); - if (!empty($pageIds[NS_FILE])) { - + if ( !empty( $pageIds[NS_FILE] ) ) { + $titles = array_keys($pageIds[NS_FILE]); + asort($titles); // Ensure the order is always the same + + $skip = false; + if(!is_null($params['continue'])) + { + $skip = true; + $cont = explode('|', $params['continue']); + if(count($cont) != 2) + $this->dieUsage("Invalid continue param. You should pass the original " . + "value returned by the previous query", "_badcontinue"); + $fromTitle = strval($cont[0]); + $fromTimestamp = $cont[1]; + // Filter out any titles before $fromTitle + foreach($titles as $key => $title) + if($title < $fromTitle) + unset($titles[$key]); + else + break; + } + $result = $this->getResult(); - $images = RepoGroup::singleton()->findFiles( array_keys( $pageIds[NS_FILE] ) ); + $images = RepoGroup::singleton()->findFiles( $titles ); foreach ( $images as $img ) { - $data = array(); - + $start = $skip ? $fromTimestamp : $params['start']; + $pageId = $pageIds[NS_IMAGE][ $img->getOriginalTitle()->getDBkey() ]; + + $fit = $result->addValue( + array('query', 'pages', intval($pageId)), + 'imagerepository', $img->getRepoName() + ); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + # The user is screwed. imageinfo can't be solely + # responsible for exceeding the limit in this case, + # so set a query-continue that just returns the same + # thing again. When the violating queries have been + # out-continued, the result will get through + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $img->getTimestamp())); + else + $this->setContinueEnumParameter('continue', + $this->getContinueStr($img)); + break; + } + // Get information about the current version first // Check that the current version is within the start-end boundaries - if((is_null($params['start']) || $img->getTimestamp() <= $params['start']) && + $gotOne = false; + if((is_null($start) || $img->getTimestamp() <= $start) && (is_null($params['end']) || $img->getTimestamp() >= $params['end'])) { - $data[] = self::getInfo( $img, $prop, $result, $scale ); + $gotOne = true; + $fit = $this->addPageSubItem($pageId, + self::getInfo( $img, $prop, $result, $scale)); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + # See the 'the user is screwed' comment above + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $img->getTimestamp())); + else + $this->setContinueEnumParameter('continue', + $this->getContinueStr($img)); + break; + } } // Now get the old revisions // Get one more to facilitate query-continue functionality - $count = count($data); - $oldies = $img->getHistory($params['limit'] - $count + 1, $params['start'], $params['end']); + $count = ($gotOne ? 1 : 0); + $oldies = $img->getHistory($params['limit'] - $count + 1, $start, $params['end']); foreach($oldies as $oldie) { if(++$count > $params['limit']) { // We've reached the extra one which shows that there are additional pages to be had. Stop here... // Only set a query-continue if there was only one title if(count($pageIds[NS_FILE]) == 1) - $this->setContinueEnumParameter('start', $oldie->getTimestamp()); + { + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $oldie->getTimestamp())); + } + break; + } + $fit = $this->addPageSubItem($pageId, + self::getInfo($oldie, $prop, $result)); + if(!$fit) + { + if(count($pageIds[NS_IMAGE]) == 1) + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $oldie->getTimestamp())); + else + $this->setContinueEnumParameter('continue', + $this->getContinueStr($oldie)); break; } - $data[] = self::getInfo( $oldie, $prop, $result ); } - - $pageId = $pageIds[NS_FILE][ $img->getOriginalTitle()->getDBkey() ]; - $result->addValue( - array( 'query', 'pages', intval( $pageId ) ), - 'imagerepository', $img->getRepoName() - ); - $this->addPageSubItems($pageId, $data); + if(!$fit) + break; + $skip = false; } $missing = array_diff( array_keys( $pageIds[NS_FILE] ), array_keys( $images ) ); - foreach ( $missing as $title ) + foreach ($missing as $title) { $result->addValue( - array( 'query', 'pages', intval( $pageIds[NS_FILE][$title] ) ), + array('query', 'pages', intval($pageIds[NS_FILE][$title])), 'imagerepository', '' ); + // The above can't fail because it doesn't increase the result size + } } } @@ -127,8 +194,8 @@ class ApiQueryImageInfo extends ApiQueryBase { if( $mto && !$mto->isError() ) { $vals['thumburl'] = $mto->getUrl(); - $vals['thumbwidth'] = $mto->getWidth(); - $vals['thumbheight'] = $mto->getHeight(); + $vals['thumbwidth'] = intval( $mto->getWidth() ); + $vals['thumbheight'] = intval( $mto->getHeight() ); } } $vals['url'] = $file->getFullURL(); @@ -140,8 +207,7 @@ class ApiQueryImageInfo extends ApiQueryBase { $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 ); if( isset( $prop['metadata'] ) ) { $metadata = $file->getMetadata(); - $vals['metadata'] = $metadata ? unserialize( $metadata ) : null; - $result->setIndexedTagName_recursive( $vals['metadata'], 'meta' ); + $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null; } if( isset( $prop['mime'] ) ) $vals['mime'] = $file->getMimeType(); @@ -154,6 +220,30 @@ class ApiQueryImageInfo extends ApiQueryBase { return $vals; } + + public static function processMetaData($metadata, $result) + { + $retval = array(); + if ( is_array( $metadata ) ) { + foreach($metadata as $key => $value) + { + $r = array('name' => $key); + if(is_array($value)) + $r['value'] = self::processMetaData($value, $result); + else + $r['value'] = $value; + $retval[] = $r; + } + } + $result->setIndexedTagName($retval, 'metadata'); + return $retval; + } + + private function getContinueStr($img) + { + return $img->getOriginalTitle()->getText() . + '|' . $img->getTimestamp(); + } public function getAllowedParams() { return array ( @@ -193,7 +283,8 @@ class ApiQueryImageInfo extends ApiQueryBase { 'urlheight' => array( ApiBase :: PARAM_TYPE => 'integer', ApiBase :: PARAM_DFLT => -1 - ) + ), + 'continue' => null, ); } @@ -206,6 +297,7 @@ class ApiQueryImageInfo extends ApiQueryBase { 'urlwidth' => array('If iiprop=url is set, a URL to an image scaled to this width will be returned.', 'Only the current version of the image can be scaled.'), 'urlheight' => 'Similar to iiurlwidth. Cannot be used without iiurlwidth', + 'continue' => 'When more results are available, use this to continue', ); } @@ -217,12 +309,12 @@ class ApiQueryImageInfo extends ApiQueryBase { protected function getExamples() { return array ( - 'api.php?action=query&titles=Image:Albert%20Einstein%20Head.jpg&prop=imageinfo', - 'api.php?action=query&titles=Image:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url', + 'api.php?action=query&titles=File:Albert%20Einstein%20Head.jpg&prop=imageinfo', + 'api.php?action=query&titles=File:Test.jpg&prop=imageinfo&iilimit=50&iiend=20071231235959&iiprop=timestamp|user|url', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImageInfo.php 44121 2008-12-01 17:14:30Z vyznev $'; + return __CLASS__ . ': $Id: ApiQueryImageInfo.php 50097 2009-05-01 06:35:57Z tstarling $'; } } diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php index 02fe24f1..69569e9b 100644 --- a/includes/api/ApiQueryImages.php +++ b/includes/api/ApiQueryImages.php @@ -82,9 +82,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { - - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if (++$count > $params['limit']) { @@ -94,23 +91,16 @@ class ApiQueryImages extends ApiQueryGeneratorBase { '|' . $this->keyToTitle($row->il_to)); break; } - if ($lastId != $row->il_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->il_from; - } - $vals = array(); ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle(NS_FILE, $row->il_to)); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->il_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->il_from . + '|' . $this->keyToTitle($row->il_to)); + break; + } } - } else { $titles = array(); @@ -165,6 +155,6 @@ class ApiQueryImages extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImages.php 44121 2008-12-01 17:14:30Z vyznev $'; + return __CLASS__ . ': $Id: ApiQueryImages.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 0c5c72fc..b7affabc 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -34,6 +34,10 @@ if (!defined('MEDIAWIKI')) { * @ingroup API */ class ApiQueryInfo extends ApiQueryBase { + + private $fld_protection = false, $fld_talkid = false, + $fld_subjectid = false, $fld_url = false, + $fld_readable = false; public function __construct($query, $moduleName) { parent :: __construct($query, $moduleName, 'in'); @@ -49,11 +53,13 @@ class ApiQueryInfo extends ApiQueryBase { $pageSet->requestField('page_len'); } + /** + * Get an array mapping token names to their handler functions. + * The prototype for a token function is func($pageid, $title) + * it should return a token or false (permission denied) + * @return array(tokenname => function) + */ protected function getTokenFunctions() { - // tokenname => function - // function prototype is func($pageid, $title) - // should return token or false - // Don't call the hooks twice if(isset($this->tokenFunctions)) return $this->tokenFunctions; @@ -70,6 +76,7 @@ class ApiQueryInfo extends ApiQueryBase { 'block' => array( 'ApiQueryInfo', 'getBlockToken' ), 'unblock' => array( 'ApiQueryInfo', 'getUnblockToken' ), 'email' => array( 'ApiQueryInfo', 'getEmailToken' ), + 'import' => array( 'ApiQueryInfo', 'getImportToken' ), ); wfRunHooks('APIQueryInfoTokens', array(&$this->tokenFunctions)); return $this->tokenFunctions; @@ -79,6 +86,7 @@ class ApiQueryInfo extends ApiQueryBase { { // We could check for $title->userCan('edit') here, // but that's too expensive for this purpose + // and would break caching global $wgUser; if(!$wgUser->isAllowed('edit')) return false; @@ -167,76 +175,201 @@ class ApiQueryInfo extends ApiQueryBase { $cachedEmailToken = $wgUser->editToken(); return $cachedEmailToken; } + + public static function getImportToken($pageid, $title) + { + global $wgUser; + if(!$wgUser->isAllowed('import')) + return false; - public function execute() { + static $cachedImportToken = null; + if(!is_null($cachedImportToken)) + return $cachedImportToken; - global $wgUser; + $cachedImportToken = $wgUser->editToken(); + return $cachedImportToken; + } - $params = $this->extractRequestParams(); - $fld_protection = $fld_talkid = $fld_subjectid = $fld_url = $fld_readable = false; - if(!is_null($params['prop'])) { - $prop = array_flip($params['prop']); - $fld_protection = isset($prop['protection']); - $fld_talkid = isset($prop['talkid']); - $fld_subjectid = isset($prop['subjectid']); - $fld_url = isset($prop['url']); - $fld_readable = isset($prop['readable']); + public function execute() { + $this->params = $this->extractRequestParams(); + if(!is_null($this->params['prop'])) { + $prop = array_flip($this->params['prop']); + $this->fld_protection = isset($prop['protection']); + $this->fld_talkid = isset($prop['talkid']); + $this->fld_subjectid = isset($prop['subjectid']); + $this->fld_url = isset($prop['url']); + $this->fld_readable = isset($prop['readable']); } $pageSet = $this->getPageSet(); - $titles = $pageSet->getGoodTitles(); - $missing = $pageSet->getMissingTitles(); + $this->titles = $pageSet->getGoodTitles(); + $this->missing = $pageSet->getMissingTitles(); + $this->everything = $this->titles + $this->missing; $result = $this->getResult(); - $pageRestrictions = $pageSet->getCustomField('page_restrictions'); - $pageIsRedir = $pageSet->getCustomField('page_is_redirect'); - $pageIsNew = $pageSet->getCustomField('page_is_new'); - $pageCounter = $pageSet->getCustomField('page_counter'); - $pageTouched = $pageSet->getCustomField('page_touched'); - $pageLatest = $pageSet->getCustomField('page_latest'); - $pageLength = $pageSet->getCustomField('page_len'); + uasort($this->everything, array('Title', 'compare')); + if(!is_null($this->params['continue'])) + { + // Throw away any titles we're gonna skip so they don't + // clutter queries + $cont = explode('|', $this->params['continue']); + if(count($cont) != 2) + $this->dieUsage("Invalid continue param. You should pass the original " . + "value returned by the previous query", "_badcontinue"); + $conttitle = Title::makeTitleSafe($cont[0], $cont[1]); + foreach($this->everything as $pageid => $title) + { + if(Title::compare($title, $conttitle) >= 0) + break; + unset($this->titles[$pageid]); + unset($this->missing[$pageid]); + unset($this->everything[$pageid]); + } + } + + $this->pageRestrictions = $pageSet->getCustomField('page_restrictions'); + $this->pageIsRedir = $pageSet->getCustomField('page_is_redirect'); + $this->pageIsNew = $pageSet->getCustomField('page_is_new'); + $this->pageCounter = $pageSet->getCustomField('page_counter'); + $this->pageTouched = $pageSet->getCustomField('page_touched'); + $this->pageLatest = $pageSet->getCustomField('page_latest'); + $this->pageLength = $pageSet->getCustomField('page_len'); $db = $this->getDB(); - if ($fld_protection && count($titles)) { - $this->addTables('page_restrictions'); - $this->addFields(array('pr_page', 'pr_type', 'pr_level', 'pr_expiry', 'pr_cascade')); - $this->addWhereFld('pr_page', array_keys($titles)); + // Get protection info if requested + if ($this->fld_protection) + $this->getProtectionInfo(); + + // Run the talkid/subjectid query if requested + if($this->fld_talkid || $this->fld_subjectid) + $this->getTSIDs(); + + foreach($this->everything as $pageid => $title) { + $pageInfo = $this->extractPageInfo($pageid, $title); + $fit = $result->addValue(array ( + 'query', + 'pages' + ), $pageid, $pageInfo); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + $title->getNamespace() . '|' . + $title->getText()); + break; + } + } + } + + /** + * Get a result array with information about a title + * @param $pageid int Page ID (negative for missing titles) + * @param $title Title object + * @return array + */ + private function extractPageInfo($pageid, $title) + { + $pageInfo = array(); + if($title->exists()) + { + $pageInfo['touched'] = wfTimestamp(TS_ISO_8601, $this->pageTouched[$pageid]); + $pageInfo['lastrevid'] = intval($this->pageLatest[$pageid]); + $pageInfo['counter'] = intval($this->pageCounter[$pageid]); + $pageInfo['length'] = intval($this->pageLength[$pageid]); + if ($this->pageIsRedir[$pageid]) + $pageInfo['redirect'] = ''; + if ($this->pageIsNew[$pageid]) + $pageInfo['new'] = ''; + } + + if (!is_null($this->params['token'])) { + $tokenFunctions = $this->getTokenFunctions(); + $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); + foreach($this->params['token'] as $t) + { + $val = call_user_func($tokenFunctions[$t], $pageid, $title); + if($val === false) + $this->setWarning("Action '$t' is not allowed for the current user"); + else + $pageInfo[$t . 'token'] = $val; + } + } + + if($this->fld_protection) { + $pageInfo['protection'] = array(); + if (isset($this->protections[$title->getNamespace()][$title->getDBkey()])) + $pageInfo['protection'] = + $this->protections[$title->getNamespace()][$title->getDBkey()]; + $this->getResult()->setIndexedTagName($pageInfo['protection'], 'pr'); + } + if($this->fld_talkid && isset($this->talkids[$title->getNamespace()][$title->getDBKey()])) + $pageInfo['talkid'] = $this->talkids[$title->getNamespace()][$title->getDBKey()]; + if($this->fld_subjectid && isset($this->subjectids[$title->getNamespace()][$title->getDBKey()])) + $pageInfo['subjectid'] = $this->subjectids[$title->getNamespace()][$title->getDBKey()]; + if($this->fld_url) { + $pageInfo['fullurl'] = $title->getFullURL(); + $pageInfo['editurl'] = $title->getFullURL('action=edit'); + } + if($this->fld_readable) + if($title->userCanRead()) + $pageInfo['readable'] = ''; + return $pageInfo; + } + + /** + * Get information about protections and put it in $protections + */ + private function getProtectionInfo() + { + $this->protections = array(); + $db = $this->getDB(); + + // Get normal protections for existing titles + if(count($this->titles)) + { + $this->addTables(array('page_restrictions', 'page')); + $this->addWhere('page_id=pr_page'); + $this->addFields(array('pr_page', 'pr_type', 'pr_level', + 'pr_expiry', 'pr_cascade', 'page_namespace', + 'page_title')); + $this->addWhereFld('pr_page', array_keys($this->titles)); $res = $this->select(__METHOD__); while($row = $db->fetchObject($res)) { $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) + 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601) ); if($row->pr_cascade) $a['cascade'] = ''; - $protections[$row->pr_page][] = $a; - + $this->protections[$row->page_namespace][$row->page_title][] = $a; + # Also check old restrictions - if($pageRestrictions[$row->pr_page]) { - foreach(explode(':', trim($pageRestrictions[$pageid])) as $restrict) { + if($this->pageRestrictions[$row->pr_page]) { + $restrictions = explode(':', trim($this->pageRestrictions[$row->pr_page])); + foreach($restrictions as $restrict) { $temp = explode('=', trim($restrict)); if(count($temp) == 1) { // old old format should be treated as edit/move restriction - $restriction = trim( $temp[0] ); + $restriction = trim($temp[0]); + if($restriction == '') continue; - $protections[$row->pr_page][] = array( + $this->protections[$row->page_namespace][$row->page_title][] = array( 'type' => 'edit', 'level' => $restriction, 'expiry' => 'infinity', ); - $protections[$row->pr_page][] = array( + $this->protections[$row->page_namespace][$row->page_title][] = array( 'type' => 'move', 'level' => $restriction, 'expiry' => 'infinity', ); } else { - $restriction = trim( $temp[1] ); + $restriction = trim($temp[1]); if($restriction == '') continue; - $protections[$row->pr_page][] = array( + $this->protections[$row->page_namespace][$row->page_title][] = array( 'type' => $temp[0], 'level' => $restriction, 'expiry' => 'infinity', @@ -246,272 +379,123 @@ class ApiQueryInfo extends ApiQueryBase { } } $db->freeResult($res); - - $imageIds = array(); - foreach ($titles as $id => $title) - if ($title->getNamespace() == NS_FILE) - $imageIds[] = $id; - // To avoid code duplication - $cascadeTypes = array( - array( - 'prefix' => 'tl', - 'table' => 'templatelinks', - 'ns' => 'tl_namespace', - 'title' => 'tl_title', - 'ids' => array_diff(array_keys($titles), $imageIds) - ), - array( - 'prefix' => 'il', - 'table' => 'imagelinks', - 'ns' => NS_FILE, - 'title' => 'il_to', - 'ids' => $imageIds - ) - ); - - foreach ($cascadeTypes as $type) - { - if (count($type['ids']) != 0) { - $this->resetQueryParams(); - $this->addTables(array('page_restrictions', $type['table'])); - $this->addTables('page', 'page_source'); - $this->addTables('page', 'page_target'); - $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', - 'page_target.page_id AS page_target_id', - 'page_source.page_namespace AS page_source_namespace', - 'page_source.page_title AS page_source_title')); - $this->addWhere(array("{$type['prefix']}_from = pr_page", - 'page_target.page_namespace = '.$type['ns'], - 'page_target.page_title = '.$type['title'], - 'page_source.page_id = pr_page' - )); - $this->addWhereFld('pr_cascade', 1); - $this->addWhereFld('page_target.page_id', $type['ids']); - - $res = $this->select(__METHOD__); - while($row = $db->fetchObject($res)) { - $source = Title::makeTitle($row->page_source_namespace, $row->page_source_title); - $a = array( - 'type' => $row->pr_type, - 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), - 'source' => $source->getPrefixedText() - ); - $protections[$row->page_target_id][] = $a; - } - $db->freeResult($res); - } - } } - // We don't need to check for pt stuff if there are no nonexistent titles - if($fld_protection && count($missing)) + // Get protections for missing titles + if(count($this->missing)) { $this->resetQueryParams(); - // Construct a custom WHERE clause that matches all titles in $missing - $lb = new LinkBatch($missing); + $lb = new LinkBatch($this->missing); $this->addTables('protected_titles'); $this->addFields(array('pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry')); $this->addWhere($lb->constructSet('pt', $db)); $res = $this->select(__METHOD__); - $prottitles = array(); while($row = $db->fetchObject($res)) { - $prottitles[$row->pt_namespace][$row->pt_title][] = array( + $this->protections[$row->pt_namespace][$row->pt_title][] = array( 'type' => 'create', 'level' => $row->pt_create_perm, 'expiry' => Block::decodeExpiry($row->pt_expiry, TS_ISO_8601) ); } $db->freeResult($res); - - $images = array(); - $others = array(); - foreach ($missing as $title) - if ($title->getNamespace() == NS_FILE) - $images[] = $title->getDBKey(); - else - $others[] = $title; - - if (count($others) != 0) { - $lb = new LinkBatch($others); - $this->resetQueryParams(); - $this->addTables(array('page_restrictions', 'page', 'templatelinks')); - $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', - 'page_title', 'page_namespace', - 'tl_title', 'tl_namespace')); - $this->addWhere($lb->constructSet('tl', $db)); - $this->addWhere('pr_page = page_id'); - $this->addWhere('pr_page = tl_from'); - $this->addWhereFld('pr_cascade', 1); - - $res = $this->select(__METHOD__); - while($row = $db->fetchObject($res)) { - $source = Title::makeTitle($row->page_namespace, $row->page_title); - $a = array( - 'type' => $row->pr_type, - 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), - 'source' => $source->getPrefixedText() - ); - $prottitles[$row->tl_namespace][$row->tl_title][] = $a; - } - $db->freeResult($res); - } - - if (count($images) != 0) { - $this->resetQueryParams(); - $this->addTables(array('page_restrictions', 'page', 'imagelinks')); - $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', - 'page_title', 'page_namespace', 'il_to')); - $this->addWhere('pr_page = page_id'); - $this->addWhere('pr_page = il_from'); - $this->addWhereFld('pr_cascade', 1); - $this->addWhereFld('il_to', $images); - - $res = $this->select(__METHOD__); - while($row = $db->fetchObject($res)) { - $source = Title::makeTitle($row->page_namespace, $row->page_title); - $a = array( - 'type' => $row->pr_type, - 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), - 'source' => $source->getPrefixedText() - ); - $prottitles[NS_FILE][$row->il_to][] = $a; - } - $db->freeResult($res); - } - } - - // Run the talkid/subjectid query - if($fld_talkid || $fld_subjectid) - { - $talktitles = $subjecttitles = - $talkids = $subjectids = array(); - $everything = array_merge($titles, $missing); - foreach($everything as $t) - { - if(MWNamespace::isTalk($t->getNamespace())) - { - if($fld_subjectid) - $subjecttitles[] = $t->getSubjectPage(); - } - else if($fld_talkid) - $talktitles[] = $t->getTalkPage(); - } - if(count($talktitles) || count($subjecttitles)) - { - // Construct a custom WHERE clause that matches - // all titles in $talktitles and $subjecttitles - $lb = new LinkBatch(array_merge($talktitles, $subjecttitles)); - $this->resetQueryParams(); - $this->addTables('page'); - $this->addFields(array('page_title', 'page_namespace', 'page_id')); - $this->addWhere($lb->constructSet('page', $db)); - $res = $this->select(__METHOD__); - while($row = $db->fetchObject($res)) - { - if(MWNamespace::isTalk($row->page_namespace)) - $talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = $row->page_id; - else - $subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = $row->page_id; - } - } } - foreach ( $titles as $pageid => $title ) { - $pageInfo = array ( - 'touched' => wfTimestamp(TS_ISO_8601, $pageTouched[$pageid]), - 'lastrevid' => intval($pageLatest[$pageid]), - 'counter' => intval($pageCounter[$pageid]), - 'length' => intval($pageLength[$pageid]), - ); - - if ($pageIsRedir[$pageid]) - $pageInfo['redirect'] = ''; - - if ($pageIsNew[$pageid]) - $pageInfo['new'] = ''; + // Cascading protections + $images = $others = array(); + foreach ($this->everything as $title) + if ($title->getNamespace() == NS_FILE) + $images[] = $title->getDBKey(); + else + $others[] = $title; + + if (count($others)) { + // Non-images: check templatelinks + $lb = new LinkBatch($others); + $this->resetQueryParams(); + $this->addTables(array('page_restrictions', 'page', 'templatelinks')); + $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', + 'page_title', 'page_namespace', + 'tl_title', 'tl_namespace')); + $this->addWhere($lb->constructSet('tl', $db)); + $this->addWhere('pr_page = page_id'); + $this->addWhere('pr_page = tl_from'); + $this->addWhereFld('pr_cascade', 1); - if (!is_null($params['token'])) { - $tokenFunctions = $this->getTokenFunctions(); - $pageInfo['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); - foreach($params['token'] as $t) - { - $val = call_user_func($tokenFunctions[$t], $pageid, $title); - if($val === false) - $this->setWarning("Action '$t' is not allowed for the current user"); - else - $pageInfo[$t . 'token'] = $val; - } + $res = $this->select(__METHOD__); + while($row = $db->fetchObject($res)) { + $source = Title::makeTitle($row->page_namespace, $row->page_title); + $this->protections[$row->tl_namespace][$row->tl_title][] = array( + 'type' => $row->pr_type, + 'level' => $row->pr_level, + 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601), + 'source' => $source->getPrefixedText() + ); } + $db->freeResult($res); + } - if($fld_protection) { - $pageInfo['protection'] = array(); - if (isset($protections[$pageid])) { - $pageInfo['protection'] = $protections[$pageid]; - $result->setIndexedTagName($pageInfo['protection'], 'pr'); - } - } - if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) - $pageInfo['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; - if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) - $pageInfo['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; - if($fld_url) { - $pageInfo['fullurl'] = $title->getFullURL(); - $pageInfo['editurl'] = $title->getFullURL('action=edit'); + if (count($images)) { + // Images: check imagelinks + $this->resetQueryParams(); + $this->addTables(array('page_restrictions', 'page', 'imagelinks')); + $this->addFields(array('pr_type', 'pr_level', 'pr_expiry', + 'page_title', 'page_namespace', 'il_to')); + $this->addWhere('pr_page = page_id'); + $this->addWhere('pr_page = il_from'); + $this->addWhereFld('pr_cascade', 1); + $this->addWhereFld('il_to', $images); + + $res = $this->select(__METHOD__); + while($row = $db->fetchObject($res)) { + $source = Title::makeTitle($row->page_namespace, $row->page_title); + $this->protections[NS_FILE][$row->il_to][] = array( + 'type' => $row->pr_type, + 'level' => $row->pr_level, + 'expiry' => Block::decodeExpiry($row->pr_expiry, TS_ISO_8601), + 'source' => $source->getPrefixedText() + ); } - if($fld_readable) - if($title->userCanRead()) - $pageInfo['readable'] = ''; - - $result->addValue(array ( - 'query', - 'pages' - ), $pageid, $pageInfo); + $db->freeResult($res); } + } - // Get properties for missing titles if requested - if(!is_null($params['token']) || $fld_protection || $fld_talkid || $fld_subjectid || - $fld_url || $fld_readable) + /** + * Get talk page IDs (if requested) and subject page IDs (if requested) + * and put them in $talkids and $subjectids + */ + private function getTSIDs() + { + $getTitles = $this->talkids = $this->subjectids = array(); + $db = $this->getDB(); + foreach($this->everything as $t) { - $res = &$result->getData(); - foreach($missing as $pageid => $title) { - if(!is_null($params['token'])) - { - $tokenFunctions = $this->getTokenFunctions(); - $res['query']['pages'][$pageid]['starttimestamp'] = wfTimestamp(TS_ISO_8601, time()); - foreach($params['token'] as $t) - { - $val = call_user_func($tokenFunctions[$t], $pageid, $title); - if($val === false) - $this->setWarning("Action '$t' is not allowed for the current user"); - else - $res['query']['pages'][$pageid][$t . 'token'] = $val; - } - } - if($fld_protection) - { - // Apparently the XML formatting code doesn't like array(null) - // This is painful to fix, so we'll just work around it - if(isset($prottitles[$title->getNamespace()][$title->getDBkey()])) - $res['query']['pages'][$pageid]['protection'] = $prottitles[$title->getNamespace()][$title->getDBkey()]; - else - $res['query']['pages'][$pageid]['protection'] = array(); - $result->setIndexedTagName($res['query']['pages'][$pageid]['protection'], 'pr'); - } - if($fld_talkid && isset($talkids[$title->getNamespace()][$title->getDBKey()])) - $res['query']['pages'][$pageid]['talkid'] = $talkids[$title->getNamespace()][$title->getDBKey()]; - if($fld_subjectid && isset($subjectids[$title->getNamespace()][$title->getDBKey()])) - $res['query']['pages'][$pageid]['subjectid'] = $subjectids[$title->getNamespace()][$title->getDBKey()]; - if($fld_url) { - $res['query']['pages'][$pageid]['fullurl'] = $title->getFullURL(); - $res['query']['pages'][$pageid]['editurl'] = $title->getFullURL('action=edit'); - } - if($fld_readable) - if($title->userCanRead()) - $res['query']['pages'][$pageid]['readable'] = ''; + if(MWNamespace::isTalk($t->getNamespace())) + { + if($this->fld_subjectid) + $getTitles[] = $t->getSubjectPage(); } + else if($this->fld_talkid) + $getTitles[] = $t->getTalkPage(); + } + if(!count($getTitles)) + return; + + // Construct a custom WHERE clause that matches + // all titles in $getTitles + $lb = new LinkBatch($getTitles); + $this->resetQueryParams(); + $this->addTables('page'); + $this->addFields(array('page_title', 'page_namespace', 'page_id')); + $this->addWhere($lb->constructSet('page', $db)); + $res = $this->select(__METHOD__); + while($row = $db->fetchObject($res)) + { + if(MWNamespace::isTalk($row->page_namespace)) + $this->talkids[MWNamespace::getSubject($row->page_namespace)][$row->page_title] = + intval($row->page_id); + else + $this->subjectids[MWNamespace::getTalk($row->page_namespace)][$row->page_title] = + intval($row->page_id); } } @@ -531,7 +515,8 @@ class ApiQueryInfo extends ApiQueryBase { ApiBase :: PARAM_DFLT => NULL, ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()) - ) + ), + 'continue' => null, ); } @@ -539,15 +524,15 @@ class ApiQueryInfo extends ApiQueryBase { return array ( 'prop' => array ( 'Which additional properties to get:', - ' "protection" - List the protection level of each page', - ' "talkid" - The page ID of the talk page for each non-talk page', - ' "subjectid" - The page ID of the parent page for each talk page' + ' protection - List the protection level of each page', + ' talkid - The page ID of the talk page for each non-talk page', + ' subjectid - The page ID of the parent page for each talk page' ), 'token' => 'Request a token to perform a data-modifying action on a page', + 'continue' => 'When more results are available, use this to continue', ); } - public function getDescription() { return 'Get basic page information such as namespace, title, last touched date, ...'; } @@ -560,6 +545,6 @@ class ApiQueryInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 45683 2009-01-12 19:10:42Z raymond $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 48488 2009-03-17 15:18:26Z catrope $'; } } diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index 8eaf8d02..3abe5e3d 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -71,8 +71,6 @@ class ApiQueryLangLinks extends ApiQueryBase { $this->addOption('LIMIT', $params['limit'] + 1); $res = $this->select(__METHOD__); - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; $db = $this->getDB(); while ($row = $db->fetchObject($res)) { @@ -82,23 +80,15 @@ class ApiQueryLangLinks extends ApiQueryBase { $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}"); break; } - if ($lastId != $row->ll_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->ll_from; - } - $entry = array('lang' => $row->ll_lang); ApiResult :: setContent($entry, $row->ll_title); - $data[] = $entry; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->ll_from, $entry); + if(!$fit) + { + $this->setContinueEnumParameter('continue', "{$row->ll_from}|{$row->ll_lang}"); + break; + } } - $db->freeResult($res); } @@ -134,6 +124,6 @@ class ApiQueryLangLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLangLinks.php 43271 2008-11-06 22:38:42Z siebrand $'; + return __CLASS__ . ': $Id: ApiQueryLangLinks.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php index 91b5b529..40a7c114 100644 --- a/includes/api/ApiQueryLinks.php +++ b/includes/api/ApiQueryLinks.php @@ -119,9 +119,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); if (is_null($resultPageSet)) { - - $data = array(); - $lastId = 0; // database has no ID 0 $count = 0; while ($row = $db->fetchObject($res)) { if(++$count > $params['limit']) { @@ -132,23 +129,17 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $this->keyToTitle($row->pl_title)); break; } - if ($lastId != $row->pl_from) { - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); - $data = array(); - } - $lastId = $row->pl_from; - } - $vals = array(); ApiQueryBase :: addTitleInfo($vals, Title :: makeTitle($row->pl_namespace, $row->pl_title)); - $data[] = $vals; - } - - if($lastId != 0) { - $this->addPageSubItems($lastId, $data); + $fit = $this->addPageSubItem($row->pl_from, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', + "{$row->pl_from}|{$row->pl_namespace}|" . + $this->keyToTitle($row->pl_title)); + break; + } } - } else { $titles = array(); @@ -213,6 +204,6 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLinks.php 43271 2008-11-06 22:38:42Z siebrand $'; + return __CLASS__ . ': $Id: ApiQueryLinks.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 83c73b83..864aaa03 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -59,18 +59,21 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addWhere($hideLogs); // Order is significant here - $this->addTables(array('user', 'page', 'logging')); + $this->addTables(array('logging', 'user', 'page')); + $this->addOption('STRAIGHT_JOIN'); $this->addJoinConds(array( + 'user' => array('JOIN', + 'user_id=log_user'), 'page' => array('LEFT JOIN', array( 'log_namespace=page_namespace', 'log_title=page_title')))); - $this->addWhere('user_id=log_user'); - $this->addOption('USE INDEX', array('logging' => 'times')); // default, may change + $index = 'times'; // default, may change $this->addFields(array ( 'log_type', 'log_action', 'log_timestamp', + 'log_deleted', )); $this->addFieldsIf('log_id', $this->fld_ids); @@ -81,20 +84,17 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addFieldsIf('log_title', $this->fld_title); $this->addFieldsIf('log_comment', $this->fld_comment); $this->addFieldsIf('log_params', $this->fld_details); - - $this->addWhereFld('log_deleted', 0); if( !is_null($params['type']) ) { $this->addWhereFld('log_type', $params['type']); - $this->addOption('USE INDEX', array('logging' => array('type_time'))); + $index = 'type_time'; } $this->addWhereRange('log_timestamp', $params['dir'], $params['start'], $params['end']); $limit = $params['limit']; $this->addOption('LIMIT', $limit +1); - - $index = false; + $user = $params['user']; if (!is_null($user)) { $userid = User::idFromName($user); @@ -113,14 +113,19 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addWhereFld('log_title', $titleObj->getDBkey()); // Use the title index in preference to the user index if there is a conflict - $index = 'page_time'; - } - if ( $index ) { - $this->addOption( 'USE INDEX', array( 'logging' => $index ) ); + $index = is_null($user) ? 'page_time' : array('page_time','user_time'); } + $this->addOption( 'USE INDEX', array( 'logging' => $index ) ); + + // Paranoia: avoid brute force searches (bug 17342) + if (!is_null($title)) { + $this->addWhere('log_deleted & ' . LogPage::DELETED_ACTION . ' = 0'); + } + if (!is_null($user)) { + $this->addWhere('log_deleted & ' . LogPage::DELETED_USER . ' = 0'); + } - $data = array (); $count = 0; $res = $this->select(__METHOD__); while ($row = $db->fetchObject($res)) { @@ -131,13 +136,18 @@ class ApiQueryLogEvents extends ApiQueryBase { } $vals = $this->extractRowInfo($row); - if($vals) - $data[] = $vals; + if(!$vals) + continue; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->log_timestamp)); + break; + } } $db->freeResult($res); - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } public static function addLogParams($result, &$vals, $params, $type, $ts) { @@ -150,9 +160,12 @@ class ApiQueryLogEvents extends ApiQueryBase { $vals2 = array(); ApiQueryBase :: addTitleInfo($vals2, $title, "new_"); $vals[$type] = $vals2; - $params = null; } } + if (isset ($params[1]) && $params[1]) { + $vals[$type]['suppressedredirect'] = ''; + } + $params = null; break; case 'patrol': $vals2 = array(); @@ -191,8 +204,12 @@ class ApiQueryLogEvents extends ApiQueryBase { } if ($this->fld_title) { - $title = Title :: makeTitle($row->log_namespace, $row->log_title); - ApiQueryBase :: addTitleInfo($vals, $title); + if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) { + $vals['actionhidden'] = ''; + } else { + $title = Title :: makeTitle($row->log_namespace, $row->log_title); + ApiQueryBase :: addTitleInfo($vals, $title); + } } if ($this->fld_type) { @@ -201,21 +218,33 @@ class ApiQueryLogEvents extends ApiQueryBase { } if ($this->fld_details && $row->log_params !== '') { - self::addLogParams($this->getResult(), $vals, - $row->log_params, $row->log_type, - $row->log_timestamp); + if (LogEventsList::isDeleted($row, LogPage::DELETED_ACTION)) { + $vals['actionhidden'] = ''; + } else { + self::addLogParams($this->getResult(), $vals, + $row->log_params, $row->log_type, + $row->log_timestamp); + } } if ($this->fld_user) { - $vals['user'] = $row->user_name; - if(!$row->log_user) - $vals['anon'] = ''; + if (LogEventsList::isDeleted($row, LogPage::DELETED_USER)) { + $vals['userhidden'] = ''; + } else { + $vals['user'] = $row->user_name; + if(!$row->log_user) + $vals['anon'] = ''; + } } if ($this->fld_timestamp) { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->log_timestamp); } if ($this->fld_comment && isset($row->log_comment)) { - $vals['comment'] = $row->log_comment; + if (LogEventsList::isDeleted($row, LogPage::DELETED_COMMENT)) { + $vals['commenthidden'] = ''; + } else { + $vals['comment'] = $row->log_comment; + } } return $vals; @@ -290,6 +319,6 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 44234 2008-12-04 15:59:26Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 47904 2009-03-01 11:02:49Z catrope $'; } } diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php new file mode 100644 index 00000000..779deee5 --- /dev/null +++ b/includes/api/ApiQueryProtectedTitles.php @@ -0,0 +1,191 @@ +<?php + +/* + * Created on Feb 13, 2009 + * + * API for MediaWiki 1.8+ + * + * Copyright (C) 2009 Roan Kattouw <Firstname>.<Lastname>@home.nl + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +if (!defined('MEDIAWIKI')) { + // Eclipse helper - will be ignored in production + require_once ('ApiQueryBase.php'); +} + +/** + * Query module to enumerate all create-protected pages. + * + * @ingroup API + */ +class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { + + public function __construct($query, $moduleName) { + parent :: __construct($query, $moduleName, 'pt'); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator($resultPageSet) { + $this->run($resultPageSet); + } + + private function run($resultPageSet = null) { + $db = $this->getDB(); + $params = $this->extractRequestParams(); + + $this->addTables('protected_titles'); + $this->addFields(array('pt_namespace', 'pt_title', 'pt_timestamp')); + + $prop = array_flip($params['prop']); + $this->addFieldsIf('pt_user', isset($prop['user'])); + $this->addFieldsIf('pt_reason', isset($prop['comment'])); + $this->addFieldsIf('pt_expiry', isset($prop['expiry'])); + $this->addFieldsIf('pt_create_perm', isset($prop['level'])); + + $this->addWhereRange('pt_timestamp', $params['dir'], $params['start'], $params['end']); + $this->addWhereFld('pt_namespace', $params['namespace']); + $this->addWhereFld('pt_create_perm', $params['level']); + + if(isset($prop['user'])) + { + $this->addTables('user'); + $this->addFields('user_name'); + $this->addJoinConds(array('user' => array('LEFT JOIN', + 'user_id=pt_user' + ))); + } + + $this->addOption('LIMIT', $params['limit'] + 1); + $res = $this->select(__METHOD__); + + $count = 0; + $result = $this->getResult(); + while ($row = $db->fetchObject($res)) { + if (++ $count > $params['limit']) { + // We've reached the one extra which shows that there are additional pages to be had. Stop here... + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->pt_timestamp)); + break; + } + + $title = Title::makeTitle($row->pt_namespace, $row->pt_title); + if (is_null($resultPageSet)) { + $vals = array(); + ApiQueryBase::addTitleInfo($vals, $title); + if(isset($prop['timestamp'])) + $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->pt_timestamp); + if(isset($prop['user']) && !is_null($row->user_name)) + $vals['user'] = $row->user_name; + if(isset($prop['comment'])) + $vals['comment'] = $row->pt_reason; + if(isset($prop['expiry'])) + $vals['expiry'] = Block::decodeExpiry($row->pt_expiry, TS_ISO_8601); + if(isset($prop['level'])) + $vals['level'] = $row->pt_create_perm; + + $fit = $result->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $row->pt_timestamp)); + break; + } + } else { + $titles[] = $title; + } + } + $db->freeResult($res); + if(is_null($resultPageSet)) + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), $this->getModulePrefix()); + else + $resultPageSet->populateFromTitles($titles); + } + + public function getAllowedParams() { + global $wgRestrictionLevels; + return array ( + 'namespace' => array ( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => 'namespace', + ), + 'level' => array( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_TYPE => array_diff($wgRestrictionLevels, array('')) + ), + 'limit' => array ( + ApiBase :: PARAM_DFLT => 10, + ApiBase :: PARAM_TYPE => 'limit', + ApiBase :: PARAM_MIN => 1, + ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1, + ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2 + ), + 'dir' => array ( + ApiBase :: PARAM_DFLT => 'older', + ApiBase :: PARAM_TYPE => array ( + 'older', + 'newer' + ) + ), + 'start' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'end' => array( + ApiBase :: PARAM_TYPE => 'timestamp' + ), + 'prop' => array( + ApiBase :: PARAM_ISMULTI => true, + ApiBase :: PARAM_DFLT => 'timestamp|level', + ApiBase :: PARAM_TYPE => array( + 'timestamp', + 'user', + 'comment', + 'expiry', + 'level' + ) + ), + ); + } + + public function getParamDescription() { + return array ( + 'namespace' => 'Only list titles in these namespaces', + 'start' => 'Start listing at this protection timestamp', + 'end' => 'Stop listing at this protection timestamp', + 'dir' => 'The direction in which to list', + 'limit' => 'How many total pages to return.', + 'prop' => 'Which properties to get', + 'level' => 'Only list titles with these protection levels', + ); + } + + public function getDescription() { + return 'List all titles protected from creation'; + } + + protected function getExamples() { + return array ( + 'api.php?action=query&list=protectedtitles', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 47235 2009-02-13 21:53:08Z catrope $'; + } +}
\ No newline at end of file diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php index e7b8bf46..73c4a81c 100644 --- a/includes/api/ApiQueryRandom.php +++ b/includes/api/ApiQueryRandom.php @@ -62,7 +62,7 @@ if (!defined('MEDIAWIKI')) { $this->addFields($resultPageSet->getPageTableFields()); } - protected function runQuery(&$data, &$resultPageSet) { + protected function runQuery(&$resultPageSet) { $db = $this->getDB(); $res = $this->select(__METHOD__); $count = 0; @@ -73,7 +73,14 @@ if (!defined('MEDIAWIKI')) { // Prevent duplicates if(!in_array($row->page_id, $this->pageIDs)) { - $data[] = $this->extractRowInfo($row); + $fit = $this->getResult()->addValue( + array('query', $this->getModuleName()), + null, $this->extractRowInfo($row)); + if(!$fit) + # We can't really query-continue a random list. + # Return an insanely high value so + # $count < $limit is false + return 1E9; $this->pageIDs[] = $row->page_id; } } @@ -87,11 +94,10 @@ if (!defined('MEDIAWIKI')) { public function run($resultPageSet = null) { $params = $this->extractRequestParams(); $result = $this->getResult(); - $data = array(); $this->pageIDs = array(); $this->prepareQuery(wfRandom(), $params['limit'], $params['namespace'], $resultPageSet, $params['redirect']); - $count = $this->runQuery($data, $resultPageSet); + $count = $this->runQuery($resultPageSet); if($count < $params['limit']) { /* We got too few pages, we probably picked a high value @@ -99,21 +105,19 @@ if (!defined('MEDIAWIKI')) { * also the comment in Title::getRandomTitle() */ $this->prepareQuery(0, $params['limit'] - $count, $params['namespace'], $resultPageSet, $params['redirect']); - $this->runQuery($data, $resultPageSet); + $this->runQuery($resultPageSet); } if(is_null($resultPageSet)) { - $result->setIndexedTagName($data, 'page'); - $result->addValue('query', $this->getModuleName(), $data); + $result->setIndexedTagName_internal(array('query', $this->getModuleName()), 'page'); } } private function extractRowInfo($row) { $title = Title::makeTitle($row->page_namespace, $row->page_title); $vals = array(); - $vals['title'] = $title->getPrefixedText(); - $vals['ns'] = $row->page_namespace; - $vals['id'] = $row->page_id; + $vals['id'] = intval($row->page_id); + ApiQueryBase::addTitleInfo($vals, $title); return $vals; } @@ -157,4 +161,4 @@ if (!defined('MEDIAWIKI')) { public function getVersion() { return __CLASS__ . ': $Id: ApiQueryRandom.php overlordq$'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 04eb910f..191eec28 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -97,25 +97,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addWhereRange('rc_timestamp', $params['dir'], $params['start'], $params['end']); $this->addWhereFld('rc_namespace', $params['namespace']); $this->addWhereFld('rc_deleted', 0); - if($params['titles']) - { - $lb = new LinkBatch; - foreach($params['titles'] as $t) - { - $obj = Title::newFromText($t); - $lb->addObj($obj); - if($obj->getNamespace() < 0) - { - // LinkBatch refuses these, but we need them anyway - if(!array_key_exists($obj->getNamespace(), $lb->data)) - $lb->data[$obj->getNamespace()] = array(); - $lb->data[$obj->getNamespace()][$obj->getDBKey()] = 1; - } - } - $where = $lb->constructSet('rc', $this->getDB()); - if($where != '') - $this->addWhere($where); - } if(!is_null($params['type'])) $this->addWhereFld('rc_type', $this->parseRCType($params['type'])); @@ -210,9 +191,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->token = $params['token']; $this->addOption('LIMIT', $params['limit'] +1); - $data = array (); $count = 0; - /* Perform the actual query. */ $db = $this->getDB(); $res = $this->select(__METHOD__); @@ -229,16 +208,20 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals = $this->extractRowInfo($row); /* Add that row's data to our final output. */ - if($vals) - $data[] = $vals; + if(!$vals) + continue; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); + break; + } } $db->freeResult($res); /* Format the result */ - $result = $this->getResult(); - $result->setIndexedTagName($data, 'rc'); - $result->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'rc'); } /** @@ -328,7 +311,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals['patrolled'] = ''; if ($this->fld_loginfo && $row->rc_type == RC_LOG) { - $vals['logid'] = $row->rc_logid; + $vals['logid'] = intval($row->rc_logid); $vals['logtype'] = $row->rc_log_type; $vals['logaction'] = $row->rc_log_action; ApiQueryLogEvents::addLogParams($this->getResult(), @@ -389,9 +372,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_TYPE => 'namespace' ), - 'titles' => array( - ApiBase :: PARAM_ISMULTI => true - ), 'prop' => array ( ApiBase :: PARAM_ISMULTI => true, ApiBase :: PARAM_DFLT => 'title|timestamp|ids', @@ -451,7 +431,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'end' => 'The timestamp to end enumerating.', 'dir' => 'In which direction to enumerate.', 'namespace' => 'Filter log entries to only this namespace(s)', - 'titles' => 'Filter log entries to only these page titles', 'prop' => 'Include additional pieces of information', 'token' => 'Which tokens to obtain for each change', 'show' => array ( @@ -474,6 +453,6 @@ class ApiQueryRecentChanges extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 44719 2008-12-17 16:34:01Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 50094 2009-05-01 06:24:09Z tstarling $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 977e792b..ca9152ad 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -100,9 +100,29 @@ class ApiQueryRevisions extends ApiQueryBase { if ($pageCount > 1 && $enumRevMode) $this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages'); + if (!is_null($params['diffto'])) { + if ($params['diffto'] == 'cur') + $params['diffto'] = 0; + if ((!ctype_digit($params['diffto']) || $params['diffto'] < 0) + && $params['diffto'] != 'prev' && $params['diffto'] != 'next') + $this->dieUsage('rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto'); + // Check whether the revision exists and is readable, + // DifferenceEngine returns a rather ambiguous empty + // string if that's not the case + if ($params['diffto'] != 0) { + $difftoRev = Revision::newFromID($params['diffto']); + if (!$difftoRev) + $this->dieUsageMsg(array('nosuchrevid', $params['diffto'])); + if (!$difftoRev->userCan(Revision::DELETED_TEXT)) { + $this->setWarning("Couldn't diff to r{$difftoRev->getID()}: content is hidden"); + $params['diffto'] = null; + } + } + } + $this->addTables('revision'); - $this->addFields( Revision::selectFields() ); - $this->addTables( 'page' ); + $this->addFields(Revision::selectFields()); + $this->addTables('page'); $this->addWhere('page_id = rev_page'); $prop = array_flip($params['prop']); @@ -116,6 +136,7 @@ class ApiQueryRevisions extends ApiQueryBase { $this->fld_size = isset ($prop['size']); $this->fld_user = isset ($prop['user']); $this->token = $params['token']; + $this->diffto = $params['diffto']; if ( !is_null($this->token) || $pageCount > 0) { $this->addFields( Revision::selectPageFields() ); @@ -134,7 +155,7 @@ class ApiQueryRevisions extends ApiQueryBase { $this->addTables('text'); $this->addWhere('rev_text_id=old_id'); $this->addFields('old_id'); - $this->addFields( Revision::selectTextFields() ); + $this->addFields(Revision::selectTextFields()); $this->fld_content = true; @@ -176,9 +197,14 @@ class ApiQueryRevisions extends ApiQueryBase { if (is_null($params['startid']) && is_null($params['endid'])) $this->addWhereRange('rev_timestamp', $params['dir'], $params['start'], $params['end']); - else + else { $this->addWhereRange('rev_id', $params['dir'], $params['startid'], $params['endid']); + // One of start and end can be set + // If neither is set, this does nothing + $this->addWhereRange('rev_timestamp', $params['dir'], + $params['start'], $params['end'], false); + } // must manually initialize unset limit if (is_null($limit)) @@ -186,14 +212,18 @@ class ApiQueryRevisions extends ApiQueryBase { $this->validateLimit('limit', $limit, 1, $userMax, $botMax); // There is only one ID, use it - $this->addWhereFld('rev_page', current(array_keys($pageSet->getGoodTitles()))); + $this->addWhereFld('rev_page', reset(array_keys($pageSet->getGoodTitles()))); if(!is_null($params['user'])) { $this->addWhereFld('rev_user_text', $params['user']); - } elseif (!is_null( $params['excludeuser'])) { + } elseif (!is_null($params['excludeuser'])) { $this->addWhere('rev_user_text != ' . $this->getDB()->addQuotes($params['excludeuser'])); } + if(!is_null($params['user']) || !is_null($params['excludeuser'])) { + // Paranoia: avoid brute force searches (bug 17342) + $this->addWhere('rev_deleted & ' . Revision::DELETED_USER . ' = 0'); + } } elseif ($revCount > 0) { $max = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; @@ -204,6 +234,10 @@ class ApiQueryRevisions extends ApiQueryBase { // Get all revision IDs $this->addWhereFld('rev_id', array_keys($revs)); + if(!is_null($params['continue'])) + $this->addWhere("rev_id >= '" . intval($params['continue']) . "'"); + $this->addOption('ORDER BY', 'rev_id'); + // assumption testing -- we should never get more then $revCount rows. $limit = $revCount; } @@ -220,6 +254,22 @@ class ApiQueryRevisions extends ApiQueryBase { // Get all page IDs $this->addWhereFld('page_id', array_keys($titles)); + // Every time someone relies on equality propagation, god kills a kitten :) + $this->addWhereFld('rev_page', array_keys($titles)); + + if(!is_null($params['continue'])) + { + $cont = explode('|', $params['continue']); + if(count($cont) != 2) + $this->dieUsage("Invalid continue param. You should pass the original " . + "value returned by the previous query", "_badcontinue"); + $pageid = intval($cont[0]); + $revid = intval($cont[1]); + $this->addWhere("rev_page > '$pageid' OR " . + "(rev_page = '$pageid' AND " . + "rev_id >= '$revid')"); + } + $this->addOption('ORDER BY', 'rev_page, rev_id'); // assumption testing -- we should never get more then $pageCount rows. $limit = $pageCount; @@ -242,37 +292,30 @@ class ApiQueryRevisions extends ApiQueryBase { $this->setContinueEnumParameter('startid', intval($row->rev_id)); break; } - $revision = new Revision( $row ); - $this->getResult()->addValue( - array ( - 'query', - 'pages', - $revision->getPage(), - 'revisions'), - null, - $this->extractRowInfo( $revision )); - } - $db->freeResult($res); - - // Ensure that all revisions are shown as '<rev>' elements - $result = $this->getResult(); - if ($result->getIsRawMode()) { - $data =& $result->getData(); - foreach ($data['query']['pages'] as & $page) { - if (is_array($page) && array_key_exists('revisions', $page)) { - $result->setIndexedTagName($page['revisions'], 'rev'); - } + // + $fit = $this->addPageSubItem($revision->getPage(), $this->extractRowInfo($revision), 'rev'); + if(!$fit) + { + if($enumRevMode) + $this->setContinueEnumParameter('startid', intval($row->rev_id)); + else if($revCount > 0) + $this->setContinueEnumParameter('continue', intval($row->rev_id)); + else + $this->setContinueEnumParameter('continue', intval($row->rev_page) . + '|' . intval($row->rev_id)); + break; } } + $db->freeResult($res); } private function extractRowInfo( $revision ) { - + $title = $revision->getTitle(); $vals = array (); if ($this->fld_ids) { - $vals['revid'] = $revision->getId(); + $vals['revid'] = intval($revision->getId()); // $vals['oldid'] = intval($row->rev_text_id); // todo: should this be exposed? } @@ -280,9 +323,13 @@ class ApiQueryRevisions extends ApiQueryBase { $vals['minor'] = ''; if ($this->fld_user) { - $vals['user'] = $revision->getUserText(); - if (!$revision->getUser()) - $vals['anon'] = ''; + if ($revision->isDeleted(Revision::DELETED_USER)) { + $vals['userhidden'] = ''; + } else { + $vals['user'] = $revision->getUserText(); + if (!$revision->getUser()) + $vals['anon'] = ''; + } } if ($this->fld_timestamp) { @@ -290,17 +337,18 @@ class ApiQueryRevisions extends ApiQueryBase { } if ($this->fld_size && !is_null($revision->getSize())) { - $vals['size'] = $revision->getSize(); + $vals['size'] = intval($revision->getSize()); } if ($this->fld_comment) { - $comment = $revision->getComment(); - if (strval($comment) !== '') - $vals['comment'] = $comment; - } - - if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates)) - $title = $revision->getTitle(); + if ($revision->isDeleted(Revision::DELETED_COMMENT)) { + $vals['commenthidden'] = ''; + } else { + $comment = $revision->getComment(); + if (strval($comment) !== '') + $vals['comment'] = $comment; + } + } if(!is_null($this->token)) { @@ -314,8 +362,8 @@ class ApiQueryRevisions extends ApiQueryBase { $vals[$t . 'token'] = $val; } } - - if ($this->fld_content) { + + if ($this->fld_content && !$revision->isDeleted(Revision::DELETED_TEXT)) { global $wgParser; $text = $revision->getText(); # Expand templates after getting section content because @@ -341,6 +389,24 @@ class ApiQueryRevisions extends ApiQueryBase { $text = $wgParser->preprocess( $text, $title, new ParserOptions() ); } ApiResult :: setContent($vals, $text); + } else if ($this->fld_content) { + $vals['texthidden'] = ''; + } + + if (!is_null($this->diffto)) { + global $wgAPIMaxUncachedDiffs; + static $n = 0; // Numer of uncached diffs we've had + if($n< $wgAPIMaxUncachedDiffs) { + $engine = new DifferenceEngine($title, $revision->getID(), $this->diffto); + $difftext = $engine->getDiffBody(); + $vals['diff']['from'] = $engine->getOldid(); + $vals['diff']['to'] = $engine->getNewid(); + ApiResult::setContent($vals['diff'], $difftext); + if(!$engine->wasCacheHit()) + $n++; + } else { + $vals['diff']['notcached'] = ''; + } } return $vals; } @@ -398,6 +464,8 @@ class ApiQueryRevisions extends ApiQueryBase { ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()), ApiBase :: PARAM_ISMULTI => true ), + 'continue' => null, + 'diffto' => null, ); } @@ -416,6 +484,9 @@ class ApiQueryRevisions extends ApiQueryBase { 'generatexml' => 'generate XML parse tree for revision content', 'section' => 'only retrieve the content of this section', 'token' => 'Which tokens to obtain for each revision', + 'continue' => 'When more results are available, use this to continue', + 'diffto' => array('Revision ID to diff each revision to.', + 'Use "prev", "next" and "cur" for the previous, next and current revision respectively.'), ); } @@ -448,6 +519,6 @@ class ApiQueryRevisions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 44719 2008-12-17 16:34:01Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 48642 2009-03-20 20:21:38Z midom $'; } } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index cb020fff..668f00e5 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -87,7 +87,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $this->dieUsage("{$what} search is disabled", "search-{$what}-disabled"); - $data = array (); + $titles = array (); $count = 0; while( $result = $matches->next() ) { if (++ $count > $limit) { @@ -102,20 +102,23 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $title = $result->getTitle(); if (is_null($resultPageSet)) { - $data[] = array( - 'ns' => intval($title->getNamespace()), - 'title' => $title->getPrefixedText()); + $vals = array(); + ApiQueryBase::addTitleInfo($vals, $title); + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('offset', $params['offset'] + $count - 1); + break; + } } else { - $data[] = $title; + $titles[] = $title; } } if (is_null($resultPageSet)) { - $result = $this->getResult(); - $result->setIndexedTagName($data, 'p'); - $result->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'p'); } else { - $resultPageSet->populateFromTitles($data); + $resultPageSet->populateFromTitles($titles); } } @@ -170,6 +173,6 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySearch.php 44186 2008-12-03 19:33:57Z catrope $'; + return __CLASS__ . ': $Id: ApiQuerySearch.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 84757f7f..6b867abb 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -41,44 +41,60 @@ class ApiQuerySiteinfo extends ApiQueryBase { public function execute() { $params = $this->extractRequestParams(); + $done = array(); foreach( $params['prop'] as $p ) { switch ( $p ) { case 'general': - $this->appendGeneralInfo( $p ); + $fit = $this->appendGeneralInfo( $p ); break; case 'namespaces': - $this->appendNamespaces( $p ); + $fit = $this->appendNamespaces( $p ); break; case 'namespacealiases': - $this->appendNamespaceAliases( $p ); + $fit = $this->appendNamespaceAliases( $p ); break; case 'specialpagealiases': - $this->appendSpecialPageAliases( $p ); + $fit = $this->appendSpecialPageAliases( $p ); break; case 'magicwords': - $this->appendMagicWords( $p ); + $fit = $this->appendMagicWords( $p ); break; case 'interwikimap': $filteriw = isset( $params['filteriw'] ) ? $params['filteriw'] : false; - $this->appendInterwikiMap( $p, $filteriw ); + $fit = $this->appendInterwikiMap( $p, $filteriw ); break; case 'dbrepllag': - $this->appendDbReplLagInfo( $p, $params['showalldb'] ); + $fit = $this->appendDbReplLagInfo( $p, $params['showalldb'] ); break; case 'statistics': - $this->appendStatistics( $p ); + $fit = $this->appendStatistics( $p ); break; case 'usergroups': - $this->appendUserGroups( $p ); + $fit = $this->appendUserGroups( $p ); break; case 'extensions': - $this->appendExtensions( $p ); + $fit = $this->appendExtensions( $p ); + break; + case 'fileextensions': + $fit = $this->appendFileExtensions( $p ); + break; + case 'rightsinfo': + $fit = $this->appendRightsInfo( $p ); break; default : ApiBase :: dieDebug( __METHOD__, "Unknown prop=$p" ); } + if(!$fit) + { + # Abuse siprop as a query-continue parameter + # and set it to all unprocessed props + $this->setContinueEnumParameter('prop', implode('|', + array_diff($params['prop'], $done))); + break; + } + $done[] = $p; } } @@ -121,9 +137,9 @@ class ApiQuerySiteinfo extends ApiQueryBase { $offset = 0; } $data['timezone'] = $tz; - $data['timeoffset'] = $offset; + $data['timeoffset'] = intval($offset); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendNamespaces( $property ) { @@ -132,7 +148,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { foreach( $wgContLang->getFormattedNamespaces() as $ns => $title ) { $data[$ns] = array( - 'id' => $ns + 'id' => intval($ns) ); ApiResult :: setContent( $data[$ns], $title ); $canonical = MWNamespace::getCanonicalName( $ns ); @@ -145,24 +161,29 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'ns' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendNamespaceAliases( $property ) { global $wgNamespaceAliases, $wgContLang; $wgContLang->load(); - $aliases = array_merge($wgNamespaceAliases, $wgContLang->namespaceAliases); + $aliases = array_merge( $wgNamespaceAliases, $wgContLang->namespaceAliases ); + $namespaces = $wgContLang->getNamespaces(); $data = array(); foreach( $aliases as $title => $ns ) { + if( $namespaces[$ns] == $title ) { + // Don't list duplicates + continue; + } $item = array( - 'id' => $ns + 'id' => intval($ns) ); ApiResult :: setContent( $item, strtr( $title, '_', ' ' ) ); $data[] = $item; } $this->getResult()->setIndexedTagName( $data, 'ns' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendSpecialPageAliases( $property ) { @@ -175,7 +196,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = $arr; } $this->getResult()->setIndexedTagName( $data, 'specialpage' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendMagicWords( $property ) { @@ -191,7 +212,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = $arr; } $this->getResult()->setIndexedTagName($data, 'magicword'); - $this->getResult()->addValue('query', $property, $data); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendInterwikiMap( $property, $filter ) { @@ -229,7 +250,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $db->freeResult( $res ); $this->getResult()->setIndexedTagName( $data, 'iw' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendDbReplLagInfo( $property, $includeAll ) { @@ -251,27 +272,30 @@ class ApiQuerySiteinfo extends ApiQueryBase { list( $host, $lag ) = wfGetLB()->getMaxLag(); $data[] = array( 'host' => $wgShowHostnames ? $host : '', - 'lag' => $lag + 'lag' => intval( $lag ) ); } $result = $this->getResult(); $result->setIndexedTagName( $data, 'db' ); - $result->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendStatistics( $property ) { + global $wgDisableCounters; $data = array(); $data['pages'] = intval( SiteStats::pages() ); $data['articles'] = intval( SiteStats::articles() ); - $data['views'] = intval( SiteStats::views() ); + if ( !$wgDisableCounters ) { + $data['views'] = intval( SiteStats::views() ); + } $data['edits'] = intval( SiteStats::edits() ); $data['images'] = intval( SiteStats::images() ); $data['users'] = intval( SiteStats::users() ); $data['activeusers'] = intval( SiteStats::activeUsers() ); $data['admins'] = intval( SiteStats::numberingroup('sysop') ); $data['jobs'] = intval( SiteStats::jobs() ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendUserGroups( $property ) { @@ -284,7 +308,18 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'group' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); + } + + protected function appendFileExtensions( $property ) { + global $wgFileExtensions; + + $data = array(); + foreach( $wgFileExtensions as $ext ) { + $data[] = array( 'ext' => $ext ); + } + $this->getResult()->setIndexedTagName( $data, 'fe' ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendExtensions( $property ) { @@ -317,7 +352,25 @@ class ApiQuerySiteinfo extends ApiQueryBase { } $this->getResult()->setIndexedTagName( $data, 'ext' ); - $this->getResult()->addValue( 'query', $property, $data ); + return $this->getResult()->addValue( 'query', $property, $data ); + } + + + protected function appendRightsInfo( $property ) { + global $wgRightsPage, $wgRightsUrl, $wgRightsText; + $title = Title::newFromText( $wgRightsPage ); + $url = $title ? $title->getFullURL() : $wgRightsUrl; + $text = $wgRightsText; + if( !$text && $title ) { + $text = $title->getPrefixedText(); + } + + $data = array( + 'url' => $url ? $url : '', + 'text' => $text ? $text : '' + ); + + return $this->getResult()->addValue( 'query', $property, $data ); } @@ -337,6 +390,8 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'statistics', 'usergroups', 'extensions', + 'fileextensions', + 'rightsinfo', ) ), 'filteriw' => array( @@ -353,16 +408,18 @@ class ApiQuerySiteinfo extends ApiQueryBase { return array( 'prop' => array( 'Which sysinfo properties to get:', - ' "general" - Overall system information', - ' "namespaces" - List of registered namespaces and their canonical names', - ' "namespacealiases" - List of registered namespace aliases', - ' "specialpagealiases" - List of special page aliases', - ' "magicwords" - List of magic words and their aliases', - ' "statistics" - Returns site statistics', - ' "interwikimap" - Returns interwiki map (optionally filtered)', - ' "dbrepllag" - Returns database server with the highest replication lag', - ' "usergroups" - Returns user groups and the associated permissions', - ' "extensions" - Returns extensions installed on the wiki', + ' general - Overall system information', + ' namespaces - List of registered namespaces and their canonical names', + ' namespacealiases - List of registered namespace aliases', + ' specialpagealiases - List of special page aliases', + ' magicwords - List of magic words and their aliases', + ' statistics - Returns site statistics', + ' interwikimap - Returns interwiki map (optionally filtered)', + ' dbrepllag - Returns database server with the highest replication lag', + ' usergroups - Returns user groups and the associated permissions', + ' extensions - Returns extensions installed on the wiki', + ' fileextensions - Returns list of file extensions allowed to be uploaded', + ' rightsinfo - Returns wiki rights (license) information if available', ), 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', 'showalldb' => 'List all database servers, not just the one lagging the most', @@ -382,6 +439,6 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 44862 2008-12-20 23:49:16Z catrope $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 48060 2009-03-05 13:52:14Z demon $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index be6c8bc4..24c73996 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -41,7 +41,8 @@ class ApiQueryContributions extends ApiQueryBase { private $params, $username; private $fld_ids = false, $fld_title = false, $fld_timestamp = false, - $fld_comment = false, $fld_flags = false; + $fld_comment = false, $fld_flags = false, + $fld_patrolled = false; public function execute() { @@ -54,6 +55,7 @@ class ApiQueryContributions extends ApiQueryBase { $this->fld_comment = isset($prop['comment']); $this->fld_flags = isset($prop['flags']); $this->fld_timestamp = isset($prop['timestamp']); + $this->fld_patrolled = isset($prop['patrolled']); // TODO: if the query is going only against the revision table, should this be done? $this->selectNamedDB('contributions', DB_SLAVE, 'contributions'); @@ -81,7 +83,6 @@ class ApiQueryContributions extends ApiQueryBase { $res = $this->select( __METHOD__ ); //Initialise some variables - $data = array (); $count = 0; $limit = $this->params['limit']; @@ -97,16 +98,21 @@ class ApiQueryContributions extends ApiQueryBase { } $vals = $this->extractRowInfo($row); - if ($vals) - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + if($this->multiUserMode) + $this->setContinueEnumParameter('continue', $this->continueStr($row)); + else + $this->setContinueEnumParameter('start', wfTimestamp(TS_ISO_8601, $row->rev_timestamp)); + break; + } } //Free the database record so the connection can get on with other stuff $db->freeResult($res); - //And send the whole shebang out as output. - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } /** @@ -132,12 +138,12 @@ class ApiQueryContributions extends ApiQueryBase { * Prepares the query and returns the limit of rows requested */ private function prepareQuery() { - - //We're after the revision table, and the corresponding page row for - //anything we retrieve. - $this->addTables(array('revision', 'page')); + // We're after the revision table, and the corresponding page + // row for anything we retrieve. We may also need the + // recentchanges row. + $tables = array('page', 'revision'); // Order may change $this->addWhere('page_id=rev_page'); - + // Handle continue parameter if($this->multiUserMode && !is_null($this->params['continue'])) { @@ -162,7 +168,8 @@ class ApiQueryContributions extends ApiQueryBase { // ... and in the specified timeframe. // Ensure the same sort order for rev_user_text and rev_timestamp // so our query is indexed - $this->addWhereRange('rev_user_text', $this->params['dir'], null, null); + if($this->multiUserMode) + $this->addWhereRange('rev_user_text', $this->params['dir'], null, null); $this->addWhereRange('rev_timestamp', $this->params['dir'], $this->params['start'], $this->params['end'] ); $this->addWhereFld('page_namespace', $this->params['namespace']); @@ -170,14 +177,17 @@ class ApiQueryContributions extends ApiQueryBase { $show = $this->params['show']; if (!is_null($show)) { $show = array_flip($show); - if (isset ($show['minor']) && isset ($show['!minor'])) + if ((isset($show['minor']) && isset($show['!minor'])) + || (isset($show['patrolled']) && isset($show['!patrolled']))) $this->dieUsage("Incorrect parameter - mutually exclusive values may not be supplied", 'show'); - $this->addWhereIf('rev_minor_edit = 0', isset ($show['!minor'])); - $this->addWhereIf('rev_minor_edit != 0', isset ($show['minor'])); + $this->addWhereIf('rev_minor_edit = 0', isset($show['!minor'])); + $this->addWhereIf('rev_minor_edit != 0', isset($show['minor'])); + $this->addWhereIf('rc_patrolled = 0', isset($show['!patrolled'])); + $this->addWhereIf('rc_patrolled != 0', isset($show['patrolled'])); } $this->addOption('LIMIT', $this->params['limit'] + 1); - $this->addOption( 'USE INDEX', array( 'revision' => 'usertext_timestamp' ) ); + $index['revision'] = 'usertext_timestamp'; // Mandatory fields: timestamp allows request continuation // ns+title checks if the user has access rights for this page @@ -187,15 +197,49 @@ class ApiQueryContributions extends ApiQueryBase { 'page_namespace', 'page_title', 'rev_user_text', - )); + )); + + if(isset($show['patrolled']) || isset($show['!patrolled']) || + $this->fld_patrolled) + { + global $wgUser; + if(!$wgUser->useRCPatrol() && !$wgUser->useNPPatrol()) + $this->dieUsage("You need the patrol right to request the patrolled flag", 'permissiondenied'); + // Use a redundant join condition on both + // timestamp and ID so we can use the timestamp + // index + $index['recentchanges'] = 'rc_user_text'; + if(isset($show['patrolled']) || isset($show['!patrolled'])) + { + // Put the tables in the right order for + // STRAIGHT_JOIN + $tables = array('revision', 'recentchanges', 'page'); + $this->addOption('STRAIGHT_JOIN'); + $this->addWhere('rc_user_text=rev_user_text'); + $this->addWhere('rc_timestamp=rev_timestamp'); + $this->addWhere('rc_this_oldid=rev_id'); + } + else + { + $tables[] = 'recentchanges'; + $this->addJoinConds(array('recentchanges' => array( + 'LEFT JOIN', array( + 'rc_user_text=rev_user_text', + 'rc_timestamp=rev_timestamp', + 'rc_this_oldid=rev_id')))); + } + } + $this->addTables($tables); + $this->addOption('USE INDEX', $index); $this->addFieldsIf('rev_page', $this->fld_ids); $this->addFieldsIf('rev_id', $this->fld_ids || $this->fld_flags); $this->addFieldsIf('page_latest', $this->fld_flags); // $this->addFieldsIf('rev_text_id', $this->fld_ids); // Should this field be exposed? $this->addFieldsIf('rev_comment', $this->fld_comment); $this->addFieldsIf('rev_minor_edit', $this->fld_flags); - $this->addFieldsIf('page_is_new', $this->fld_flags); + $this->addFieldsIf('rev_parent_id', $this->fld_flags); + $this->addFieldsIf('rc_patrolled', $this->fld_patrolled); } /** @@ -220,7 +264,7 @@ class ApiQueryContributions extends ApiQueryBase { $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rev_timestamp); if ($this->fld_flags) { - if ($row->page_is_new) + if ($row->rev_parent_id == 0) $vals['new'] = ''; if ($row->rev_minor_edit) $vals['minor'] = ''; @@ -228,9 +272,12 @@ class ApiQueryContributions extends ApiQueryBase { $vals['top'] = ''; } - if ($this->fld_comment && isset( $row->rev_comment ) ) + if ($this->fld_comment && isset($row->rev_comment)) $vals['comment'] = $row->rev_comment; + if ($this->fld_patrolled && $row->rc_patrolled) + $vals['patrolled'] = ''; + return $vals; } @@ -279,7 +326,8 @@ class ApiQueryContributions extends ApiQueryBase { 'title', 'timestamp', 'comment', - 'flags' + 'flags', + 'patrolled', ) ), 'show' => array ( @@ -287,6 +335,8 @@ class ApiQueryContributions extends ApiQueryBase { ApiBase :: PARAM_TYPE => array ( 'minor', '!minor', + 'patrolled', + '!patrolled', ) ), ); @@ -303,7 +353,8 @@ class ApiQueryContributions extends ApiQueryBase { 'dir' => 'The direction to search (older or newer).', 'namespace' => 'Only list contributions in these namespaces', 'prop' => 'Include additional pieces of information', - 'show' => 'Show only items that meet this criteria, e.g. non minor edits only: show=!minor', + 'show' => array('Show only items that meet this criteria, e.g. non minor edits only: show=!minor', + 'NOTE: if show=patrolled or show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown',), ); } @@ -319,6 +370,6 @@ class ApiQueryContributions extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 43271 2008-11-06 22:38:42Z siebrand $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 47037 2009-02-09 14:07:18Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 203b7e25..ac99ad6d 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -57,7 +57,7 @@ class ApiQueryUserInfo extends ApiQueryBase { global $wgUser; $result = $this->getResult(); $vals = array(); - $vals['id'] = $wgUser->getId(); + $vals['id'] = intval($wgUser->getId()); $vals['name'] = $wgUser->getName(); if($wgUser->isAnon()) @@ -87,11 +87,17 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['preferencestoken'] = $wgUser->editToken(); } if (isset($this->prop['editcount'])) { - $vals['editcount'] = $wgUser->getEditCount(); + $vals['editcount'] = intval($wgUser->getEditCount()); } if (isset($this->prop['ratelimits'])) { $vals['ratelimits'] = $this->getRateLimits(); } + if (isset($this->prop['email'])) { + $vals['email'] = $wgUser->getEmail(); + $auth = $wgUser->getEmailAuthenticationTimestamp(); + if(!is_null($auth)) + $vals['emailauthenticated'] = wfTimestamp(TS_ISO_8601, $auth); + } return $vals; } @@ -122,8 +128,8 @@ class ApiQueryUserInfo extends ApiQueryBase { foreach($categories as $cat) if(isset($limits[$cat]) && !is_null($limits[$cat])) { - $retval[$action][$cat]['hits'] = $limits[$cat][0]; - $retval[$action][$cat]['seconds'] = $limits[$cat][1]; + $retval[$action][$cat]['hits'] = intval($limits[$cat][0]); + $retval[$action][$cat]['seconds'] = intval($limits[$cat][1]); } return $retval; } @@ -141,7 +147,8 @@ class ApiQueryUserInfo extends ApiQueryBase { 'options', 'preferencestoken', 'editcount', - 'ratelimits' + 'ratelimits', + 'email', ) ) ); @@ -174,6 +181,6 @@ class ApiQueryUserInfo extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserInfo.php 43764 2008-11-20 15:15:00Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 47865 2009-02-27 16:03:01Z catrope $'; } } diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index e50d8d82..b8aa60e7 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -51,82 +51,91 @@ if (!defined('MEDIAWIKI')) { $this->prop = array(); } - if(is_array($params['users'])) { - $r = $this->getOtherUsersInfo($params['users']); - $result->setIndexedTagName($r, 'user'); - } - $result->addValue("query", $this->getModuleName(), $r); - } - - protected function getOtherUsersInfo($users) { - $goodNames = $retval = array(); + $users = (array)$params['users']; + $goodNames = $done = array(); + $result = $this->getResult(); // Canonicalize user names foreach($users as $u) { $n = User::getCanonicalName($u); if($n === false || $n === '') - $retval[] = array('name' => $u, 'invalid' => ''); + { + $vals = array('name' => $u, 'invalid' => ''); + $fit = $result->addValue(array('query', $this->getModuleName()), + null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('users', + implode('|', array_diff($users, $done))); + $goodNames = array(); + break; + } + $done[] = $u; + } else $goodNames[] = $n; } - if(!count($goodNames)) - return $retval; - - $db = $this->getDB(); - $this->addTables('user', 'u1'); - $this->addFields('u1.*'); - $this->addWhereFld('u1.user_name', $goodNames); - - if(isset($this->prop['groups'])) { - $this->addTables('user_groups'); - $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id'))); - $this->addFields('ug_group'); - } - if(isset($this->prop['blockinfo'])) { - $this->addTables('ipblocks'); - $this->addTables('user', 'u2'); - $u2 = $this->getAliasedName('user', 'u2'); - $this->addJoinConds(array( - 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'), - $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id'))); - $this->addFields(array('ipb_reason', 'u2.user_name blocker_name')); - } + if(count($goodNames)) + { + $db = $this->getDb(); + $this->addTables('user', 'u1'); + $this->addFields('u1.*'); + $this->addWhereFld('u1.user_name', $goodNames); + + if(isset($this->prop['groups'])) { + $this->addTables('user_groups'); + $this->addJoinConds(array('user_groups' => array('LEFT JOIN', 'ug_user=u1.user_id'))); + $this->addFields('ug_group'); + } + if(isset($this->prop['blockinfo'])) { + $this->addTables('ipblocks'); + $this->addTables('user', 'u2'); + $u2 = $this->getAliasedName('user', 'u2'); + $this->addJoinConds(array( + 'ipblocks' => array('LEFT JOIN', 'ipb_user=u1.user_id'), + $u2 => array('LEFT JOIN', 'ipb_by=u2.user_id'))); + $this->addFields(array('ipb_reason', 'u2.user_name AS blocker_name')); + } - $data = array(); - $res = $this->select(__METHOD__); - while(($r = $db->fetchObject($res))) { - $user = User::newFromRow($r); - $name = $user->getName(); - $data[$name]['name'] = $name; - if(isset($this->prop['editcount'])) - // No proper member function in User class for this - $data[$name]['editcount'] = $r->user_editcount; - if(isset($this->prop['registration'])) - // Nor for this one - $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $r->user_registration); - if(isset($this->prop['groups'])) - // This row contains only one group, others will be added from other rows - if(!is_null($r->ug_group)) + $data = array(); + $res = $this->select(__METHOD__); + while(($r = $db->fetchObject($res))) { + $user = User::newFromRow($r); + $name = $user->getName(); + $data[$name]['name'] = $name; + if(isset($this->prop['editcount'])) + $data[$name]['editcount'] = intval($user->getEditCount()); + if(isset($this->prop['registration'])) + $data[$name]['registration'] = wfTimestampOrNull(TS_ISO_8601, $user->getRegistration()); + if(isset($this->prop['groups']) && !is_null($r->ug_group)) + // This row contains only one group, others will be added from other rows $data[$name]['groups'][] = $r->ug_group; - if(isset($this->prop['blockinfo'])) - if(!is_null($r->blocker_name)) { + if(isset($this->prop['blockinfo']) && !is_null($r->blocker_name)) { $data[$name]['blockedby'] = $r->blocker_name; $data[$name]['blockreason'] = $r->ipb_reason; } - if(isset($this->prop['emailable']) && $user->canReceiveEmail()) - $data[$name]['emailable'] = ''; + if(isset($this->prop['emailable']) && $user->canReceiveEmail()) + $data[$name]['emailable'] = ''; + } } - // Second pass: add result data to $retval foreach($goodNames as $u) { if(!isset($data[$u])) - $retval[] = array('name' => $u, 'missing' => ''); + $data[$u] = array('name' => $u, 'missing' => ''); else { if(isset($this->prop['groups']) && isset($data[$u]['groups'])) $this->getResult()->setIndexedTagName($data[$u]['groups'], 'g'); - $retval[] = $data[$u]; } + $fit = $result->addValue(array('query', $this->getModuleName()), + null, $data[$u]); + if(!$fit) + { + $this->setContinueEnumParameter('users', + implode('|', array_diff($users, $done))); + break; + } + $done[] = $u; } - return $retval; + return $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'user'); } public function getAllowedParams() { @@ -171,6 +180,6 @@ if (!defined('MEDIAWIKI')) { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUsers.php 44231 2008-12-04 14:42:30Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryUsers.php 50094 2009-05-01 06:24:09Z tstarling $'; } } diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index ed3482fb..b3949102 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -92,6 +92,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addFieldsIf('rc_new', $this->fld_flags); $this->addFieldsIf('rc_minor', $this->fld_flags); + $this->addFieldsIf('rc_bot', $this->fld_flags); $this->addFieldsIf('rc_user', $this->fld_user); $this->addFieldsIf('rc_user_text', $this->fld_user); $this->addFieldsIf('rc_comment', $this->fld_comment); @@ -168,7 +169,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addOption('LIMIT', $params['limit'] +1); - $data = array (); + $ids = array (); $count = 0; $res = $this->select(__METHOD__); @@ -182,13 +183,18 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if (is_null($resultPageSet)) { $vals = $this->extractRowInfo($row); - if ($vals) - $data[] = $vals; + $fit = $this->getResult()->addValue(array('query', $this->getModuleName()), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('start', + wfTimestamp(TS_ISO_8601, $row->rc_timestamp)); + break; + } } else { if ($params['allrev']) { - $data[] = intval($row->rc_this_oldid); + $ids[] = intval($row->rc_this_oldid); } else { - $data[] = intval($row->rc_cur_id); + $ids[] = intval($row->rc_cur_id); } } } @@ -196,13 +202,12 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $db->freeResult($res); if (is_null($resultPageSet)) { - $this->getResult()->setIndexedTagName($data, 'item'); - $this->getResult()->addValue('query', $this->getModuleName(), $data); + $this->getResult()->setIndexedTagName_internal(array('query', $this->getModuleName()), 'item'); } elseif ($params['allrev']) { - $resultPageSet->populateFromRevisionIDs($data); + $resultPageSet->populateFromRevisionIDs($ids); } else { - $resultPageSet->populateFromPageIDs($data); + $resultPageSet->populateFromPageIDs($ids); } } @@ -229,6 +234,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $vals['new'] = ''; if ($row->rc_minor) $vals['minor'] = ''; + if ($row->rc_bot) + $vals['bot'] = ''; } if ($this->fld_patrol && isset($row->rc_patrolled)) @@ -237,8 +244,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if ($this->fld_timestamp) $vals['timestamp'] = wfTimestamp(TS_ISO_8601, $row->rc_timestamp); - $this->addFieldsIf('rc_new_len', $this->fld_sizes); - if ($this->fld_sizes) { $vals['oldlen'] = intval($row->rc_old_len); $vals['newlen'] = intval($row->rc_new_len); @@ -338,6 +343,6 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 44719 2008-12-17 16:34:01Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 47865 2009-02-27 16:03:01Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php index e9951b42..54bb5a35 100644 --- a/includes/api/ApiQueryWatchlistRaw.php +++ b/includes/api/ApiQueryWatchlistRaw.php @@ -89,7 +89,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { $res = $this->select(__METHOD__); $db = $this->getDB(); - $data = array(); $titles = array(); $count = 0; while($row = $db->fetchObject($res)) @@ -108,16 +107,19 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { ApiQueryBase::addTitleInfo($vals, $t); if(isset($prop['changed']) && !is_null($row->wl_notificationtimestamp)) $vals['changed'] = wfTimestamp(TS_ISO_8601, $row->wl_notificationtimestamp); - $data[] = $vals; + $fit = $this->getResult()->addValue($this->getModuleName(), null, $vals); + if(!$fit) + { + $this->setContinueEnumParameter('continue', $row->wl_namespace . '|' . + $this->keyToTitle($row->wl_title)); + break; + } } else $titles[] = $t; } if(is_null($resultPageSet)) - { - $this->getResult()->setIndexedTagName($data, 'wr'); - $this->getResult()->addValue(null, $this->getModuleName(), $data); - } + $this->getResult()->setIndexedTagName_internal($this->getModuleName(), 'wr'); else $resultPageSet->populateFromTitles($titles); } @@ -174,6 +176,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 41651 2008-10-04 14:30:33Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 46845 2009-02-05 14:30:59Z catrope $'; } -} +}
\ No newline at end of file diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 900953e0..3dbee08a 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -47,14 +47,16 @@ if (!defined('MEDIAWIKI')) { */ class ApiResult extends ApiBase { - private $mData, $mIsRawMode; + private $mData, $mIsRawMode, $mSize, $mCheckingSize; /** - * Constructor - */ + * Constructor + * @param $main ApiMain object + */ public function __construct($main) { parent :: __construct($main, 'result'); $this->mIsRawMode = false; + $this->mCheckingSize = true; $this->reset(); } @@ -63,6 +65,7 @@ class ApiResult extends ApiBase { */ public function reset() { $this->mData = array (); + $this->mSize = 0; } /** @@ -74,22 +77,68 @@ class ApiResult extends ApiBase { } /** - * Returns true if the result is being created for the formatter that requested raw data. + * Returns true whether the formatter requested raw data. + * @return bool */ public function getIsRawMode() { return $this->mIsRawMode; } /** - * Get result's internal data array + * Get the result's internal data array (read-only) + * @return array */ - public function & getData() { + public function getData() { return $this->mData; } + + /** + * Get the 'real' size of a result item. This means the strlen() of the item, + * or the sum of the strlen()s of the elements if the item is an array. + * @param $value mixed + * @return int + */ + public static function size($value) { + $s = 0; + if(is_array($value)) + foreach($value as $v) + $s += self::size($v); + else if(!is_object($value)) + // Objects can't always be cast to string + $s = strlen($value); + return $s; + } + + /** + * Get the size of the result, i.e. the amount of bytes in it + * @return int + */ + public function getSize() { + return $this->mSize; + } + + /** + * Disable size checking in addValue(). Don't use this unless you + * REALLY know what you're doing. Values added while size checking + * was disabled will not be counted (ever) + */ + public function disableSizeCheck() { + $this->mCheckingSize = false; + } + + /** + * Re-enable size checking in addValue() + */ + public function enableSizeCheck() { + $this->mCheckingSize = true; + } /** * Add an output value to the array by name. * Verifies that value with the same name has not been added before. + * @param $arr array to add $value to + * @param $name string Index of $arr to add $value at + * @param $value mixed */ public static function setElement(& $arr, $name, $value) { if ($arr === null || $name === null || $value === null || !is_array($arr) || is_array($name)) @@ -109,10 +158,12 @@ class ApiResult extends ApiBase { } /** - * Adds the content element to the array. + * Adds a content element to an array. * Use this function instead of hardcoding the '*' element. - * @param string $subElemName when present, content element is created as a sub item of the arr. - * Use this parameter to create elements in format <elem>text</elem> without attributes + * @param $arr array to add the content element to + * @param $subElemName string when present, content element is created + * as a sub item of $arr. Use this parameter to create elements in + * format <elem>text</elem> without attributes */ public static function setContent(& $arr, $value, $subElemName = null) { if (is_array($value)) @@ -128,7 +179,10 @@ class ApiResult extends ApiBase { /** * In case the array contains indexed values (in addition to named), - * all indexed values will have the given tag name. + * give all indexed values the given tag name. This function MUST be + * called on every arrray that has numerical indexes. + * @param $arr array + * @param $tag string Tag name */ public function setIndexedTagName(& $arr, $tag) { // In raw mode, add the '_element', otherwise just ignore @@ -141,7 +195,9 @@ class ApiResult extends ApiBase { } /** - * Calls setIndexedTagName() on $arr and each sub-array + * Calls setIndexedTagName() on each sub-array of $arr + * @param $arr array + * @param $tag string Tag name */ public function setIndexedTagName_recursive(&$arr, $tag) { @@ -157,14 +213,41 @@ class ApiResult extends ApiBase { } /** + * Calls setIndexedTagName() on an array already in the result. + * Don't specify a path to a value that's not in the result, or + * you'll get nasty errors. + * @param $path array Path to the array, like addValue()'s $path + * @param $tag string + */ + public function setIndexedTagName_internal( $path, $tag ) { + $data = & $this->mData; + foreach((array)$path as $p) { + if ( !isset( $data[$p] ) ) { + $data[$p] = array(); + } + $data = & $data[$p]; + } + if(is_null($data)) + return; + $this->setIndexedTagName($data, $tag); + } + + /** * Add value to the output data at the given path. * Path is an indexed array, each element specifing the branch at which to add the new value * Setting $path to array('a','b','c') is equivalent to data['a']['b']['c'] = $value * If $name is empty, the $value is added as a next list element data[] = $value + * @return bool True if $value fits in the result, false if not */ public function addValue($path, $name, $value) { - - $data = & $this->getData(); + global $wgAPIMaxResultSize; + $data = & $this->mData; + if( $this->mCheckingSize ) { + $newsize = $this->mSize + self::size($value); + if($newsize > $wgAPIMaxResultSize) + return false; + $this->mSize = $newsize; + } if (!is_null($path)) { if (is_array($path)) { @@ -184,6 +267,26 @@ class ApiResult extends ApiBase { $data[] = $value; // Add list element else ApiResult :: setElement($data, $name, $value); // Add named element + return true; + } + + /** + * Unset a value previously added to the result set. + * Fails silently if the value isn't found. + * For parameters, see addValue() + * @param $path array + * @param $name string + */ + public function unsetValue($path, $name) { + $data = & $this->mData; + if(!is_null($path)) + foreach((array)$path as $p) { + if(!isset($data[$p])) + return; + $data = & $data[$p]; + } + $this->mSize -= self::size($data[$name]); + unset($data[$name]); } /** @@ -191,8 +294,17 @@ class ApiResult extends ApiBase { */ public function cleanUpUTF8() { - $data = & $this->getData(); - array_walk_recursive($data, array('UtfNormal', 'cleanUp')); + array_walk_recursive($this->mData, array('ApiResult', 'cleanUp_helper')); + } + + /** + * Callback function for cleanUpUTF8() + */ + private static function cleanUp_helper(&$s) + { + if(!is_string($s)) + return; + $s = UtfNormal::cleanUp($s); } public function execute() { @@ -200,7 +312,7 @@ class ApiResult extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 45752 2009-01-14 21:36:57Z catrope $'; + return __CLASS__ . ': $Id: ApiResult.php 47447 2009-02-18 12:41:28Z tstarling $'; } } diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php index 653dca9e..0f0eae10 100644 --- a/includes/api/ApiRollback.php +++ b/includes/api/ApiRollback.php @@ -37,7 +37,6 @@ class ApiRollback extends ApiBase { } public function execute() { - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); $titleObj = NULL; @@ -68,15 +67,15 @@ class ApiRollback extends ApiBase { if($retval) // We don't care about multiple errors, just report one of them - $this->dieUsageMsg(current($retval)); + $this->dieUsageMsg(reset($retval)); $info = array( 'title' => $titleObj->getPrefixedText(), - 'pageid' => $details['current']->getPage(), + 'pageid' => intval($details['current']->getPage()), 'summary' => $details['summary'], - 'revid' => $titleObj->getLatestRevID(), - 'old_revid' => $details['current']->getID(), - 'last_revid' => $details['target']->getID() + 'revid' => intval($titleObj->getLatestRevID()), + 'old_revid' => intval($details['current']->getID()), + 'last_revid' => intval($details['target']->getID()) ); $this->getResult()->addValue(null, $this->getModuleName(), $info); @@ -84,6 +83,10 @@ class ApiRollback extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'title' => null, @@ -119,6 +122,6 @@ class ApiRollback extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiRollback.php 45043 2008-12-26 04:13:47Z mrzman $'; + return __CLASS__ . ': $Id: ApiRollback.php 48122 2009-03-07 12:58:41Z catrope $'; } } diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index cd52c518..9216317a 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -44,7 +44,6 @@ class ApiUnblock extends ApiBase { */ public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); if($params['gettoken']) @@ -72,7 +71,7 @@ class ApiUnblock extends ApiBase { if($retval) $this->dieUsageMsg($retval); - $res['id'] = $id; + $res['id'] = intval($id); $res['user'] = $user; $res['reason'] = $reason; $this->getResult()->addValue(null, $this->getModuleName(), $res); @@ -80,6 +79,10 @@ class ApiUnblock extends ApiBase { public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'id' => null, @@ -114,6 +117,6 @@ class ApiUnblock extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiUnblock.php 42651 2008-10-27 12:06:49Z catrope $'; + return __CLASS__ . ': $Id: ApiUnblock.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php index 7ae9a3c0..ddc9f7f8 100644 --- a/includes/api/ApiUndelete.php +++ b/includes/api/ApiUndelete.php @@ -38,7 +38,6 @@ class ApiUndelete extends ApiBase { public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); $params = $this->extractRequestParams(); $titleObj = NULL; @@ -78,14 +77,18 @@ class ApiUndelete extends ApiBase { array($titleObj, array(), $wgUser, $params['reason']) ); $info['title'] = $titleObj->getPrefixedText(); - $info['revisions'] = $retval[0]; - $info['fileversions'] = $retval[1]; - $info['reason'] = $retval[2]; + $info['revisions'] = intval($retval[0]); + $info['fileversions'] = intval($retval[1]); + $info['reason'] = intval($retval[2]); $this->getResult()->addValue(null, $this->getModuleName(), $info); } public function mustBePosted() { return true; } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'title' => null, @@ -121,6 +124,6 @@ class ApiUndelete extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiUndelete.php 43270 2008-11-06 22:30:55Z siebrand $'; + return __CLASS__ . ': $Id: ApiUndelete.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index ab122fea..1b98fb86 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -29,8 +29,7 @@ if (!defined('MEDIAWIKI')) { } /** - * API module to allow users to log out of the wiki. API equivalent of - * Special:Userlogout. + * API module to allow users to watch a page * * @ingroup API */ @@ -42,7 +41,6 @@ class ApiWatch extends ApiBase { public function execute() { global $wgUser; - $this->getMain()->requestWriteMode(); if(!$wgUser->isLoggedIn()) $this->dieUsage('You must be logged-in to have a watchlist', 'notloggedin'); $params = $this->extractRequestParams(); @@ -66,6 +64,10 @@ class ApiWatch extends ApiBase { $this->getResult()->addValue(null, $this->getModuleName(), $res); } + public function isWriteMode() { + return true; + } + public function getAllowedParams() { return array ( 'title' => null, @@ -94,6 +96,6 @@ class ApiWatch extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiWatch.php 40460 2008-09-04 22:20:32Z ialex $'; + return __CLASS__ . ': $Id: ApiWatch.php 48091 2009-03-06 13:49:44Z catrope $'; } } diff --git a/includes/db/Database.php b/includes/db/Database.php index 84b88643..52a59c11 100644 --- a/includes/db/Database.php +++ b/includes/db/Database.php @@ -26,6 +26,7 @@ class Database { #------------------------------------------------------------------------------ protected $mLastQuery = ''; + protected $mDoneWrites = false; protected $mPHPError = false; protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; @@ -210,7 +211,14 @@ class Database { * @return String */ function lastQuery() { return $this->mLastQuery; } - + + + /** + * Returns true if the connection may have been used for write queries. + * Should return true if unsure. + */ + function doneWrites() { return $this->mDoneWrites; } + /** * Is a connection to the database open? * @return Boolean @@ -493,6 +501,14 @@ class Database { } /** + * Determine whether a query writes to the DB. + * Should return true if unsure. + */ + function isWriteQuery( $sql ) { + return !preg_match( '/^(?:SELECT|BEGIN|COMMIT|SET|SHOW)\b/i', $sql ); + } + + /** * Usually aborts on failure. If errors are explicitly ignored, returns success. * * @param $sql String: SQL query @@ -527,6 +543,11 @@ class Database { } $this->mLastQuery = $sql; + if ( !$this->mDoneWrites && $this->isWriteQuery( $sql ) ) { + // Set a flag indicating that writes have been done + wfDebug( __METHOD__.": Writes done: $sql\n" ); + $this->mDoneWrites = true; + } # Add a comment for easy SHOW PROCESSLIST interpretation #if ( $fname ) { @@ -566,11 +587,15 @@ class Database { } } + if ( istainted( $sql ) & TC_MYSQL ) { + throw new MWException( 'Tainted query found' ); + } + # Do the query and handle errors $ret = $this->doQuery( $commentedSql ); # Try reconnecting if the connection was lost - if ( false === $ret && ( $this->lastErrno() == 2013 || $this->lastErrno() == 2006 ) ) { + if ( false === $ret && $this->wasErrorReissuable() ) { # Transaction is gone, like it or not $this->mTrxLevel = 0; wfDebug( "Connection lost, reconnecting...\n" ); @@ -1191,6 +1216,7 @@ class Database { # SHOW INDEX should work for 3.x and up: # http://dev.mysql.com/doc/mysql/en/SHOW_INDEX.html $table = $this->tableName( $table ); + $index = $this->indexName( $index ); $sql = 'SHOW INDEX FROM '.$table; $res = $this->query( $sql, $fname ); if ( !$res ) { @@ -1396,7 +1422,7 @@ class Database { } else { $list .= $field." IN (".$this->makeList($value).") "; } - } elseif( is_null($value) ) { + } elseif( $value === null ) { if ( $mode == LIST_AND || $mode == LIST_OR ) { $list .= "$field IS "; } elseif ( $mode == LIST_SET ) { @@ -1574,6 +1600,23 @@ class Database { } /** + * Get the name of an index in a given table + */ + function indexName( $index ) { + // Backwards-compatibility hack + $renamed = array( + 'ar_usertext_timestamp' => 'usertext_timestamp', + 'un_user_id' => 'user_id', + 'un_user_ip' => 'user_ip', + ); + if( isset( $renamed[$index] ) ) { + return $renamed[$index]; + } else { + return $index; + } + } + + /** * Wrapper for addslashes() * @param $s String: to be slashed. * @return String: slashed string. @@ -1587,7 +1630,7 @@ class Database { * Otherwise returns as-is */ function addQuotes( $s ) { - if ( is_null( $s ) ) { + if ( $s === null ) { return 'NULL'; } else { # This will also quote numeric values. This should be harmless, @@ -1602,6 +1645,7 @@ class Database { * Escape string for safe LIKE usage */ function escapeLike( $s ) { + $s=str_replace('\\','\\\\',$s); $s=$this->strencode( $s ); $s=str_replace(array('%','_'),array('\%','\_'),$s); return $s; @@ -1621,7 +1665,7 @@ class Database { * PostgreSQL doesn't have them and returns "" */ function useIndexClause( $index ) { - return "FORCE INDEX ($index)"; + return "FORCE INDEX (" . $this->indexName( $index ) . ")"; } /** @@ -1817,6 +1861,14 @@ class Database { } /** + * Determines if the last query error was something that should be dealt + * with by pinging the connection and reissuing the query + */ + function wasErrorReissuable() { + return $this->lastErrno() == 2013 || $this->lastErrno() == 2006; + } + + /** * Perform a deadlock-prone transaction. * * This function invokes a callback function to perform a set of write @@ -2250,8 +2302,12 @@ class Database { } // Table prefixes - $ins = preg_replace_callback( '/\/\*(?:\$wgDBprefix|_)\*\/([a-zA-Z_0-9]*)/', - array( &$this, 'tableNameCallback' ), $ins ); + $ins = preg_replace_callback( '!/\*(?:\$wgDBprefix|_)\*/([a-zA-Z_0-9]*)!', + array( $this, 'tableNameCallback' ), $ins ); + + // Index names + $ins = preg_replace_callback( '!/\*i\*/([a-zA-Z_0-9]*)!', + array( $this, 'indexNameCallback' ), $ins ); return $ins; } @@ -2263,6 +2319,13 @@ class Database { return $this->tableName( $matches[1] ); } + /** + * Index name callback + */ + protected function indexNameCallback( $matches ) { + return $this->indexName( $matches[1] ); + } + /* * Build a concatenation list to feed into a SQL query */ @@ -2480,44 +2543,27 @@ class DBConnectionError extends DBError { } function getPageTitle() { - global $wgSitename; - return "$wgSitename has a problem"; + global $wgSitename, $wgLang; + $header = "$wgSitename has a problem"; + if ( $wgLang instanceof Language ) { + $header = htmlspecialchars( $wgLang->getMessage( 'dberr-header' ) ); + } + + return $header; } function getHTML() { - global $wgTitle, $wgUseFileCache, $title, $wgInputEncoding; - global $wgSitename, $wgServer, $wgMessageCache; - - # I give up, Brion is right. Getting the message cache to work when there is no DB is tricky. - # Hard coding strings instead. + global $wgLang, $wgMessageCache, $wgUseFileCache; - $noconnect = "<p><strong>Sorry! This site is experiencing technical difficulties.</strong></p><p>Try waiting a few minutes and reloading.</p><p><small>(Can't contact the database server: $1)</small></p>"; - $mainpage = 'Main Page'; - $searchdisabled = <<<EOT -<p style="margin: 1.5em 2em 1em">$wgSitename search is disabled for performance reasons. You can search via Google in the meantime. -<span style="font-size: 89%; display: block; margin-left: .2em">Note that their indexes of $wgSitename content may be out of date.</span></p>', -EOT; + $sorry = 'Sorry! This site is experiencing technical difficulties.'; + $again = 'Try waiting a few minutes and reloading.'; + $info = '(Can\'t contact the database server: $1)'; - $googlesearch = " -<!-- SiteSearch Google --> -<FORM method=GET action=\"http://www.google.com/search\"> -<TABLE bgcolor=\"#FFFFFF\"><tr><td> -<A HREF=\"http://www.google.com/\"> -<IMG SRC=\"http://www.google.com/logos/Logo_40wht.gif\" -border=\"0\" ALT=\"Google\"></A> -</td> -<td> -<INPUT TYPE=text name=q size=31 maxlength=255 value=\"$1\"> -<INPUT type=submit name=btnG VALUE=\"Google Search\"> -<font size=-1> -<input type=hidden name=domains value=\"$wgServer\"><br /><input type=radio name=sitesearch value=\"\"> WWW <input type=radio name=sitesearch value=\"$wgServer\" checked> $wgServer <br /> -<input type='hidden' name='ie' value='$2'> -<input type='hidden' name='oe' value='$2'> -</font> -</td></tr></TABLE> -</FORM> -<!-- SiteSearch Google -->"; - $cachederror = "The following is a cached copy of the requested page, and may not be up to date. "; + if ( $wgLang instanceof Language ) { + $sorry = htmlspecialchars( $wgLang->getMessage( 'dberr-problems' ) ); + $again = htmlspecialchars( $wgLang->getMessage( 'dberr-again' ) ); + $info = htmlspecialchars( $wgLang->getMessage( 'dberr-info' ) ); + } # No database access if ( is_object( $wgMessageCache ) ) { @@ -2528,6 +2574,7 @@ border=\"0\" ALT=\"Google\"></A> $this->error = $this->db->getProperty('mServer'); } + $noconnect = "<p><strong>$sorry</strong><br />$again</p><p><small>$info</small></p>"; $text = str_replace( '$1', $this->error, $noconnect ); /* @@ -2537,38 +2584,95 @@ border=\"0\" ALT=\"Google\"></A> "</p>\n"; }*/ - if($wgUseFileCache) { - if($wgTitle) { - $t =& $wgTitle; - } else { - if($title) { - $t = Title::newFromURL( $title ); - } elseif (@/**/$_REQUEST['search']) { - $search = $_REQUEST['search']; - return $searchdisabled . - str_replace( array( '$1', '$2' ), array( htmlspecialchars( $search ), - $wgInputEncoding ), $googlesearch ); - } else { - $t = Title::newFromText( $mainpage ); + $extra = $this->searchForm(); + + if( $wgUseFileCache ) { + $cache = $this->fileCachedPage(); + # Cached version on file system? + if( $cache !== null ) { + # Hack: extend the body for error messages + $cache = str_replace( array('</html>','</body>'), '', $cache ); + # Add cache notice... + $cachederror = "This is a cached copy of the requested page, and may not be up to date. "; + # Localize it if possible... + if( $wgLang instanceof Language ) { + $cachederror = htmlspecialchars( $wgLang->getMessage( 'dberr-cachederror' ) ); } + $warning = "<div style='color:red;font-size:150%;font-weight:bold;'>$cachederror</div>"; + # Output cached page with notices on bottom and re-close body + return "{$cache}{$warning}<hr />$text<hr />$extra</body></html>"; } + } + # Headers needed here - output is just the error message + return $this->htmlHeader()."$text<hr />$extra".$this->htmlFooter(); + } - $cache = new HTMLFileCache( $t ); - if( $cache->isFileCached() ) { - // @todo, FIXME: $msg is not defined on the next line. - $msg = '<p style="color: red"><b>'.$text."<br />\n" . - $cachederror . "</b></p>\n"; - - $tag = '<div id="article">'; - $text = str_replace( - $tag, - $tag . $text, - $cache->fetchPageText() ); - } + function searchForm() { + global $wgSitename, $wgServer, $wgLang, $wgInputEncoding; + $usegoogle = "You can try searching via Google in the meantime."; + $outofdate = "Note that their indexes of our content may be out of date."; + $googlesearch = "Search"; + + if ( $wgLang instanceof Language ) { + $usegoogle = htmlspecialchars( $wgLang->getMessage( 'dberr-usegoogle' ) ); + $outofdate = htmlspecialchars( $wgLang->getMessage( 'dberr-outofdate' ) ); + $googlesearch = htmlspecialchars( $wgLang->getMessage( 'searchbutton' ) ); + } + + $search = htmlspecialchars(@$_REQUEST['search']); + + $trygoogle = <<<EOT +<div style="margin: 1.5em">$usegoogle<br /> +<small>$outofdate</small></div> +<!-- SiteSearch Google --> +<form method="get" action="http://www.google.com/search" id="googlesearch"> + <input type="hidden" name="domains" value="$wgServer" /> + <input type="hidden" name="num" value="50" /> + <input type="hidden" name="ie" value="$wgInputEncoding" /> + <input type="hidden" name="oe" value="$wgInputEncoding" /> + + <img src="http://www.google.com/logos/Logo_40wht.gif" alt="" style="float:left; margin-left: 1.5em; margin-right: 1.5em;" /> + + <input type="text" name="q" size="31" maxlength="255" value="$search" /> + <input type="submit" name="btnG" value="$googlesearch" /> + <div> + <input type="radio" name="sitesearch" id="gwiki" value="$wgServer" checked="checked" /><label for="gwiki">$wgSitename</label> + <input type="radio" name="sitesearch" id="gWWW" value="" /><label for="gWWW">WWW</label> + </div> +</form> +<!-- SiteSearch Google --> +EOT; + return $trygoogle; + } + + function fileCachedPage() { + global $wgTitle, $title, $wgLang, $wgOut; + if( $wgOut->isDisabled() ) return; // Done already? + $mainpage = 'Main Page'; + if ( $wgLang instanceof Language ) { + $mainpage = htmlspecialchars( $wgLang->getMessage( 'mainpage' ) ); } - return $text; + if($wgTitle) { + $t =& $wgTitle; + } elseif($title) { + $t = Title::newFromURL( $title ); + } else { + $t = Title::newFromText( $mainpage ); + } + + $cache = new HTMLFileCache( $t ); + if( $cache->isFileCached() ) { + return $cache->fetchPageText(); + } else { + return ''; + } } + + function htmlBodyOnly() { + return true; + } + } /** @@ -2656,7 +2760,7 @@ class ResultWrapper implements Iterator { * Get the number of rows in a result object */ function numRows() { - return $this->db->numRows( $this->result ); + return $this->db->numRows( $this ); } /** @@ -2669,7 +2773,7 @@ class ResultWrapper implements Iterator { * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchObject() { - return $this->db->fetchObject( $this->result ); + return $this->db->fetchObject( $this ); } /** @@ -2681,14 +2785,14 @@ class ResultWrapper implements Iterator { * @throws DBUnexpectedError Thrown if the database returns an error */ function fetchRow() { - return $this->db->fetchRow( $this->result ); + return $this->db->fetchRow( $this ); } /** * Free a result object */ function free() { - $this->db->freeResult( $this->result ); + $this->db->freeResult( $this ); unset( $this->result ); unset( $this->db ); } @@ -2698,7 +2802,7 @@ class ResultWrapper implements Iterator { * See mysql_data_seek() */ function seek( $row ) { - $this->db->dataSeek( $this->result, $row ); + $this->db->dataSeek( $this, $row ); } /********************* @@ -2709,7 +2813,7 @@ class ResultWrapper implements Iterator { function rewind() { if ($this->numRows()) { - $this->db->dataSeek($this->result, 0); + $this->db->dataSeek($this, 0); } $this->pos = 0; $this->currentRow = null; diff --git a/includes/db/DatabaseIbm_db2.php b/includes/db/DatabaseIbm_db2.php new file mode 100644 index 00000000..fcd0bc2d --- /dev/null +++ b/includes/db/DatabaseIbm_db2.php @@ -0,0 +1,1796 @@ +<?php +/** + * This script is the IBM DB2 database abstraction layer + * + * See maintenance/ibm_db2/README for development notes and other specific information + * @ingroup Database + * @file + * @author leo.petr+mediawiki@gmail.com + */ + +/** + * Utility class for generating blank objects + * Intended as an equivalent to {} in Javascript + * @ingroup Database + */ +class BlankObject { +} + +/** + * This represents a column in a DB2 database + * @ingroup Database + */ +class IBM_DB2Field { + private $name, $tablename, $type, $nullable, $max_length; + + /** + * Builder method for the class + * @param Object $db Database interface + * @param string $table table name + * @param string $field column name + * @return IBM_DB2Field + */ + static function fromText($db, $table, $field) { + global $wgDBmwschema; + + $q = <<<END +SELECT +lcase(coltype) AS typname, +nulls AS attnotnull, length AS attlen +FROM sysibm.syscolumns +WHERE tbcreator=%s AND tbname=%s AND name=%s; +END; + $res = $db->query(sprintf($q, + $db->addQuotes($wgDBmwschema), + $db->addQuotes($table), + $db->addQuotes($field))); + $row = $db->fetchObject($res); + if (!$row) + return null; + $n = new IBM_DB2Field; + $n->type = $row->typname; + $n->nullable = ($row->attnotnull == 'N'); + $n->name = $field; + $n->tablename = $table; + $n->max_length = $row->attlen; + return $n; + } + /** + * Get column name + * @return string column name + */ + function name() { return $this->name; } + /** + * Get table name + * @return string table name + */ + function tableName() { return $this->tablename; } + /** + * Get column type + * @return string column type + */ + function type() { return $this->type; } + /** + * Can column be null? + * @return bool true or false + */ + function nullable() { return $this->nullable; } + /** + * How much can you fit in the column per row? + * @return int length + */ + function maxLength() { return $this->max_length; } +} + +/** + * Wrapper around binary large objects + * @ingroup Database + */ +class IBM_DB2Blob { + private $mData; + + function __construct($data) { + $this->mData = $data; + } + + function getData() { + return $this->mData; + } +} + +/** + * Primary database interface + * @ingroup Database + */ +class DatabaseIbm_db2 extends Database { + /* + * Inherited members + protected $mLastQuery = ''; + protected $mPHPError = false; + + protected $mServer, $mUser, $mPassword, $mConn = null, $mDBname; + protected $mOut, $mOpened = false; + + protected $mFailFunction; + protected $mTablePrefix; + protected $mFlags; + protected $mTrxLevel = 0; + protected $mErrorCount = 0; + protected $mLBInfo = array(); + protected $mFakeSlaveLag = null, $mFakeMaster = false; + * + */ + + /// Server port for uncataloged connections + protected $mPort = NULL; + /// Whether connection is cataloged + protected $mCataloged = NULL; + /// Schema for tables, stored procedures, triggers + protected $mSchema = NULL; + /// Whether the schema has been applied in this session + protected $mSchemaSet = false; + /// Result of last query + protected $mLastResult = NULL; + /// Number of rows affected by last INSERT/UPDATE/DELETE + protected $mAffectedRows = NULL; + /// Number of rows returned by last SELECT + protected $mNumRows = NULL; + + + const CATALOGED = "cataloged"; + const UNCATALOGED = "uncataloged"; + const USE_GLOBAL = "get from global"; + + /// Last sequence value used for a primary key + protected $mInsertId = NULL; + + /* + * These can be safely inherited + * + * Getter/Setter: (18) + * failFunction + * setOutputPage + * bufferResults + * ignoreErrors + * trxLevel + * errorCount + * getLBInfo + * setLBInfo + * lastQuery + * isOpen + * setFlag + * clearFlag + * getFlag + * getProperty + * getDBname + * getServer + * tableNameCallback + * tablePrefix + * + * Administrative: (8) + * debug + * installErrorHandler + * restoreErrorHandler + * connectionErrorHandler + * reportConnectionError + * sourceFile + * sourceStream + * replaceVars + * + * Database: (5) + * query + * set + * selectField + * generalizeSQL + * update + * strreplace + * deadlockLoop + * + * Prepared Statement: 6 + * prepare + * freePrepared + * execute + * safeQuery + * fillPrepared + * fillPreparedArg + * + * Slave/Master: (4) + * masterPosWait + * getSlavePos + * getMasterPos + * getLag + * + * Generation: (9) + * tableNames + * tableNamesN + * tableNamesWithUseIndexOrJOIN + * escapeLike + * delete + * insertSelect + * timestampOrNull + * resultObject + * aggregateValue + * selectSQLText + * selectRow + * makeUpdateOptions + * + * Reflection: (1) + * indexExists + */ + + /* + * These need to be implemented TODO + * + * Administrative: 7 / 7 + * constructor [Done] + * open [Done] + * openCataloged [Done] + * close [Done] + * newFromParams [Done] + * openUncataloged [Done] + * setup_database [Done] + * + * Getter/Setter: 13 / 13 + * cascadingDeletes [Done] + * cleanupTriggers [Done] + * strictIPs [Done] + * realTimestamps [Done] + * impliciGroupby [Done] + * implicitOrderby [Done] + * searchableIPs [Done] + * functionalIndexes [Done] + * getWikiID [Done] + * isOpen [Done] + * getServerVersion [Done] + * getSoftwareLink [Done] + * getSearchEngine [Done] + * + * Database driver wrapper: 23 / 23 + * lastError [Done] + * lastErrno [Done] + * doQuery [Done] + * tableExists [Done] + * fetchObject [Done] + * fetchRow [Done] + * freeResult [Done] + * numRows [Done] + * numFields [Done] + * fieldName [Done] + * insertId [Done] + * dataSeek [Done] + * affectedRows [Done] + * selectDB [Done] + * strencode [Done] + * conditional [Done] + * wasDeadlock [Done] + * ping [Done] + * getStatus [Done] + * setTimeout [Done] + * lock [Done] + * unlock [Done] + * insert [Done] + * select [Done] + * + * Slave/master: 2 / 2 + * setFakeSlaveLag [Done] + * setFakeMaster [Done] + * + * Reflection: 6 / 6 + * fieldExists [Done] + * indexInfo [Done] + * fieldInfo [Done] + * fieldType [Done] + * indexUnique [Done] + * textFieldSize [Done] + * + * Generation: 16 / 16 + * tableName [Done] + * addQuotes [Done] + * makeList [Done] + * makeSelectOptions [Done] + * estimateRowCount [Done] + * nextSequenceValue [Done] + * useIndexClause [Done] + * replace [Done] + * deleteJoin [Done] + * lowPriorityOption [Done] + * limitResult [Done] + * limitResultForUpdate [Done] + * timestamp [Done] + * encodeBlob [Done] + * decodeBlob [Done] + * buildConcat [Done] + */ + + ###################################### + # Getters and Setters + ###################################### + + /** + * Returns true if this database supports (and uses) cascading deletes + */ + function cascadingDeletes() { + return true; + } + + /** + * Returns true if this database supports (and uses) triggers (e.g. on the page table) + */ + function cleanupTriggers() { + return true; + } + + /** + * Returns true if this database is strict about what can be put into an IP field. + * Specifically, it uses a NULL value instead of an empty string. + */ + function strictIPs() { + return true; + } + + /** + * Returns true if this database uses timestamps rather than integers + */ + function realTimestamps() { + return true; + } + + /** + * Returns true if this database does an implicit sort when doing GROUP BY + */ + function implicitGroupby() { + return false; + } + + /** + * Returns true if this database does an implicit order by when the column has an index + * For example: SELECT page_title FROM page LIMIT 1 + */ + function implicitOrderby() { + return false; + } + + /** + * Returns true if this database can do a native search on IP columns + * e.g. this works as expected: .. WHERE rc_ip = '127.42.12.102/32'; + */ + function searchableIPs() { + return true; + } + + /** + * Returns true if this database can use functional indexes + */ + function functionalIndexes() { + return true; + } + + /** + * Returns a unique string representing the wiki on the server + */ + function getWikiID() { + if( $this->mSchema ) { + return "{$this->mDBname}-{$this->mSchema}"; + } else { + return $this->mDBname; + } + } + + + ###################################### + # Setup + ###################################### + + + /** + * + * @param string $server hostname of database server + * @param string $user username + * @param string $password + * @param string $dbName database name on the server + * @param function $failFunction (optional) + * @param integer $flags database behaviour flags (optional, unused) + */ + public function DatabaseIbm_db2($server = false, $user = false, $password = false, + $dbName = false, $failFunction = false, $flags = 0, + $schema = self::USE_GLOBAL ) + { + + global $wgOut, $wgDBmwschema; + # Can't get a reference if it hasn't been set yet + if ( !isset( $wgOut ) ) { + $wgOut = NULL; + } + $this->mOut =& $wgOut; + $this->mFailFunction = $failFunction; + $this->mFlags = DBO_TRX | $flags; + + if ( $schema == self::USE_GLOBAL ) { + $this->mSchema = $wgDBmwschema; + } + else { + $this->mSchema = $schema; + } + + $this->open( $server, $user, $password, $dbName); + } + + /** + * Opens a database connection and returns it + * Closes any existing connection + * @return a fresh connection + * @param string $server hostname + * @param string $user + * @param string $password + * @param string $dbName database name + */ + public function open( $server, $user, $password, $dbName ) + { + // Load the port number + global $wgDBport_db2, $wgDBcataloged; + wfProfileIn( __METHOD__ ); + + // Load IBM DB2 driver if missing + if (!@extension_loaded('ibm_db2')) { + @dl('ibm_db2.so'); + } + // Test for IBM DB2 support, to avoid suppressed fatal error + if ( !function_exists( 'db2_connect' ) ) { + $error = "DB2 functions missing, have you enabled the ibm_db2 extension for PHP?\n"; + wfDebug($error); + $this->reportConnectionError($error); + } + + if (!strlen($user)) { // Copied from Postgres + return null; + } + + // Close existing connection + $this->close(); + // Cache conn info + $this->mServer = $server; + $this->mPort = $port = $wgDBport_db2; + $this->mUser = $user; + $this->mPassword = $password; + $this->mDBname = $dbName; + $this->mCataloged = $cataloged = $wgDBcataloged; + + if ( $cataloged == self::CATALOGED ) { + $this->openCataloged($dbName, $user, $password); + } + elseif ( $cataloged == self::UNCATALOGED ) { + $this->openUncataloged($dbName, $user, $password, $server, $port); + } + // Don't do this + // Not all MediaWiki code is transactional + // Rather, turn it off in the begin function and turn on after a commit + // db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF); + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + + if ( $this->mConn == false ) { + wfDebug( "DB connection error\n" ); + wfDebug( "Server: $server, Database: $dbName, User: $user, Password: " . substr( $password, 0, 3 ) . "...\n" ); + wfDebug( $this->lastError()."\n" ); + return null; + } + + $this->mOpened = true; + $this->applySchema(); + + wfProfileOut( __METHOD__ ); + return $this->mConn; + } + + /** + * Opens a cataloged database connection, sets mConn + */ + protected function openCataloged( $dbName, $user, $password ) + { + @$this->mConn = db2_connect($dbName, $user, $password); + } + + /** + * Opens an uncataloged database connection, sets mConn + */ + protected function openUncataloged( $dbName, $user, $password, $server, $port ) + { + $str = "DRIVER={IBM DB2 ODBC DRIVER};"; + $str .= "DATABASE=$dbName;"; + $str .= "HOSTNAME=$server;"; + if ($port) $str .= "PORT=$port;"; + $str .= "PROTOCOL=TCPIP;"; + $str .= "UID=$user;"; + $str .= "PWD=$password;"; + + @$this->mConn = db2_connect($str, $user, $password); + } + + /** + * Closes a database connection, if it is open + * Returns success, true if already closed + */ + public function close() { + $this->mOpened = false; + if ( $this->mConn ) { + if ($this->trxLevel() > 0) { + $this->commit(); + } + return db2_close( $this->mConn ); + } + else { + return true; + } + } + + /** + * Returns a fresh instance of this class + * @static + * @return + * @param string $server hostname of database server + * @param string $user username + * @param string $password + * @param string $dbName database name on the server + * @param function $failFunction (optional) + * @param integer $flags database behaviour flags (optional, unused) + */ + static function newFromParams( $server, $user, $password, $dbName, $failFunction = false, $flags = 0) + { + return new DatabaseIbm_db2( $server, $user, $password, $dbName, $failFunction, $flags ); + } + + /** + * Retrieves the most current database error + * Forces a database rollback + */ + public function lastError() { + if ($this->lastError2()) { + $this->rollback(); + return true; + } + return false; + } + + private function lastError2() { + $connerr = db2_conn_errormsg(); + if ($connerr) return $connerr; + $stmterr = db2_stmt_errormsg(); + if ($stmterr) return $stmterr; + if ($this->mConn) return "No open connection."; + if ($this->mOpened) return "No open connection allegedly."; + + return false; + } + + /** + * Get the last error number + * Return 0 if no error + * @return integer + */ + public function lastErrno() { + $connerr = db2_conn_error(); + if ($connerr) return $connerr; + $stmterr = db2_stmt_error(); + if ($stmterr) return $stmterr; + return 0; + } + + /** + * Is a database connection open? + * @return + */ + public function isOpen() { return $this->mOpened; } + + /** + * The DBMS-dependent part of query() + * @param $sql String: SQL query. + * @return object Result object to feed to fetchObject, fetchRow, ...; or false on failure + * @access private + */ + /*private*/ + public function doQuery( $sql ) { + //print "<li><pre>$sql</pre></li>"; + // Switch into the correct namespace + $this->applySchema(); + + $ret = db2_exec( $this->mConn, $sql ); + if( !$ret ) { + print "<br><pre>"; + print $sql; + print "</pre><br>"; + $error = db2_stmt_errormsg(); + throw new DBUnexpectedError($this, 'SQL error: ' . htmlspecialchars( $error ) ); + } + $this->mLastResult = $ret; + $this->mAffectedRows = NULL; // Not calculated until asked for + return $ret; + } + + /** + * @return string Version information from the database + */ + public function getServerVersion() { + $info = db2_server_info( $this->mConn ); + return $info->DBMS_VER; + } + + /** + * Queries whether a given table exists + * @return boolean + */ + public function tableExists( $table ) { + $schema = $this->mSchema; + $sql = <<< EOF +SELECT COUNT(*) FROM SYSIBM.SYSTABLES ST +WHERE ST.NAME = '$table' AND ST.CREATOR = '$schema' +EOF; + $res = $this->query( $sql ); + if (!$res) return false; + + // If the table exists, there should be one of it + @$row = $this->fetchRow($res); + $count = $row[0]; + if ($count == '1' or $count == 1) { + return true; + } + + return false; + } + + /** + * Fetch the next row from the given result object, in object form. + * Fields can be retrieved with $row->fieldname, with fields acting like + * member variables. + * + * @param $res SQL result object as returned from Database::query(), etc. + * @return DB2 row object + * @throws DBUnexpectedError Thrown if the database returns an error + */ + public function fetchObject( $res ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + @$row = db2_fetch_object( $res ); + if( $this->lastErrno() ) { + throw new DBUnexpectedError( $this, 'Error in fetchObject(): ' . htmlspecialchars( $this->lastError() ) ); + } + // Make field names lowercase for compatibility with MySQL + if ($row) + { + $row2 = new BlankObject(); + foreach ($row as $key => $value) + { + $keyu = strtolower($key); + $row2->$keyu = $value; + } + $row = $row2; + } + return $row; + } + + /** + * Fetch the next row from the given result object, in associative array + * form. Fields are retrieved with $row['fieldname']. + * + * @param $res SQL result object as returned from Database::query(), etc. + * @return DB2 row object + * @throws DBUnexpectedError Thrown if the database returns an error + */ + public function fetchRow( $res ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + @$row = db2_fetch_array( $res ); + if ( $this->lastErrno() ) { + throw new DBUnexpectedError( $this, 'Error in fetchRow(): ' . htmlspecialchars( $this->lastError() ) ); + } + return $row; + } + + /** + * Override if introduced to base Database class + */ + public function initial_setup() { + // do nothing + } + + /** + * Create tables, stored procedures, and so on + */ + public function setup_database() { + // Timeout was being changed earlier due to mysterious crashes + // Changing it now may cause more problems than not changing it + //set_time_limit(240); + try { + // TODO: switch to root login if available + + // Switch into the correct namespace + $this->applySchema(); + $this->begin(); + + $res = dbsource( "../maintenance/ibm_db2/tables.sql", $this); + $res = null; + + // TODO: update mediawiki_version table + + // TODO: populate interwiki links + + $this->commit(); + } + catch (MWException $mwe) + { + print "<br><pre>$mwe</pre><br>"; + } + } + + /** + * Escapes strings + * Doesn't escape numbers + * @param string s string to escape + * @return escaped string + */ + public function addQuotes( $s ) { + //wfDebug("DB2::addQuotes($s)\n"); + if ( is_null( $s ) ) { + return "NULL"; + } else if ($s instanceof Blob) { + return "'".$s->fetch($s)."'"; + } + $s = $this->strencode($s); + if ( is_numeric($s) ) { + return $s; + } + else { + return "'$s'"; + } + } + + /** + * Escapes strings + * Only escapes numbers going into non-numeric fields + * @param string s string to escape + * @return escaped string + */ + public function addQuotesSmart( $table, $field, $s ) { + if ( is_null( $s ) ) { + return "NULL"; + } else if ($s instanceof Blob) { + return "'".$s->fetch($s)."'"; + } + $s = $this->strencode($s); + if ( is_numeric($s) ) { + // Check with the database if the column is actually numeric + // This allows for numbers in titles, etc + $res = $this->doQuery("SELECT $field FROM $table FETCH FIRST 1 ROWS ONLY"); + $type = db2_field_type($res, strtoupper($field)); + if ( $this->is_numeric_type( $type ) ) { + //wfDebug("DB2: Numeric value going in a numeric column: $s in $type $field in $table\n"); + return $s; + } + else { + wfDebug("DB2: Numeric in non-numeric: '$s' in $type $field in $table\n"); + return "'$s'"; + } + } + else { + return "'$s'"; + } + } + + /** + * Verifies that a DB2 column/field type is numeric + * @return bool true if numeric + * @param string $type DB2 column type + */ + public function is_numeric_type( $type ) { + switch (strtoupper($type)) { + case 'SMALLINT': + case 'INTEGER': + case 'INT': + case 'BIGINT': + case 'DECIMAL': + case 'REAL': + case 'DOUBLE': + case 'DECFLOAT': + return true; + } + return false; + } + + /** + * Alias for addQuotes() + * @param string s string to escape + * @return escaped string + */ + public function strencode( $s ) { + // Bloody useless function + // Prepends backslashes to \x00, \n, \r, \, ', " and \x1a. + // But also necessary + $s = db2_escape_string($s); + // Wide characters are evil -- some of them look like ' + $s = utf8_encode($s); + // Fix its stupidity + $from = array("\\\\", "\\'", '\\n', '\\t', '\\"', '\\r'); + $to = array("\\", "''", "\n", "\t", '"', "\r"); + $s = str_replace($from, $to, $s); // DB2 expects '', not \' escaping + return $s; + } + + /** + * Switch into the database schema + */ + protected function applySchema() { + if ( !($this->mSchemaSet) ) { + $this->mSchemaSet = true; + $this->begin(); + $this->doQuery("SET SCHEMA = $this->mSchema"); + $this->commit(); + } + } + + /** + * Start a transaction (mandatory) + */ + public function begin() { + // turn off auto-commit + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_OFF); + $this->mTrxLevel = 1; + } + + /** + * End a transaction + * Must have a preceding begin() + */ + public function commit() { + db2_commit($this->mConn); + // turn auto-commit back on + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + $this->mTrxLevel = 0; + } + + /** + * Cancel a transaction + */ + public function rollback() { + db2_rollback($this->mConn); + // turn auto-commit back on + // not sure if this is appropriate + db2_autocommit($this->mConn, DB2_AUTOCOMMIT_ON); + $this->mTrxLevel = 0; + } + + /** + * Makes an encoded list of strings from an array + * $mode: + * LIST_COMMA - comma separated, no field names + * LIST_AND - ANDed WHERE clause (without the WHERE) + * LIST_OR - ORed WHERE clause (without the WHERE) + * LIST_SET - comma separated with field names, like a SET clause + * LIST_NAMES - comma separated field names + */ + public function makeList( $a, $mode = LIST_COMMA ) { + wfDebug("DB2::makeList()\n"); + if ( !is_array( $a ) ) { + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); + } + + $first = true; + $list = ''; + foreach ( $a as $field => $value ) { + if ( !$first ) { + if ( $mode == LIST_AND ) { + $list .= ' AND '; + } elseif($mode == LIST_OR) { + $list .= ' OR '; + } else { + $list .= ','; + } + } else { + $first = false; + } + if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { + $list .= "($value)"; + } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { + $list .= "$value"; + } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { + if( count( $value ) == 0 ) { + throw new MWException( __METHOD__.': empty input' ); + } elseif( count( $value ) == 1 ) { + // Special-case single values, as IN isn't terribly efficient + // Don't necessarily assume the single key is 0; we don't + // enforce linear numeric ordering on other arrays here. + $value = array_values( $value ); + $list .= $field." = ".$this->addQuotes( $value[0] ); + } else { + $list .= $field." IN (".$this->makeList($value).") "; + } + } elseif( is_null($value) ) { + if ( $mode == LIST_AND || $mode == LIST_OR ) { + $list .= "$field IS "; + } elseif ( $mode == LIST_SET ) { + $list .= "$field = "; + } + $list .= 'NULL'; + } else { + if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { + $list .= "$field = "; + } + if ( $mode == LIST_NAMES ) { + $list .= $value; + } + // Leo: Can't insert quoted numbers into numeric columns + // (?) Might cause other problems. May have to check column type before insertion. + else if ( is_numeric($value) ) { + $list .= $value; + } + else { + $list .= $this->addQuotes( $value ); + } + } + } + return $list; + } + + /** + * Makes an encoded list of strings from an array + * Quotes numeric values being inserted into non-numeric fields + * @return string + * @param string $table name of the table + * @param array $a list of values + * @param $mode: + * LIST_COMMA - comma separated, no field names + * LIST_AND - ANDed WHERE clause (without the WHERE) + * LIST_OR - ORed WHERE clause (without the WHERE) + * LIST_SET - comma separated with field names, like a SET clause + * LIST_NAMES - comma separated field names + */ + public function makeListSmart( $table, $a, $mode = LIST_COMMA ) { + if ( !is_array( $a ) ) { + throw new DBUnexpectedError( $this, 'Database::makeList called with incorrect parameters' ); + } + + $first = true; + $list = ''; + foreach ( $a as $field => $value ) { + if ( !$first ) { + if ( $mode == LIST_AND ) { + $list .= ' AND '; + } elseif($mode == LIST_OR) { + $list .= ' OR '; + } else { + $list .= ','; + } + } else { + $first = false; + } + if ( ($mode == LIST_AND || $mode == LIST_OR) && is_numeric( $field ) ) { + $list .= "($value)"; + } elseif ( ($mode == LIST_SET) && is_numeric( $field ) ) { + $list .= "$value"; + } elseif ( ($mode == LIST_AND || $mode == LIST_OR) && is_array($value) ) { + if( count( $value ) == 0 ) { + throw new MWException( __METHOD__.': empty input' ); + } elseif( count( $value ) == 1 ) { + // Special-case single values, as IN isn't terribly efficient + // Don't necessarily assume the single key is 0; we don't + // enforce linear numeric ordering on other arrays here. + $value = array_values( $value ); + $list .= $field." = ".$this->addQuotes( $value[0] ); + } else { + $list .= $field." IN (".$this->makeList($value).") "; + } + } elseif( is_null($value) ) { + if ( $mode == LIST_AND || $mode == LIST_OR ) { + $list .= "$field IS "; + } elseif ( $mode == LIST_SET ) { + $list .= "$field = "; + } + $list .= 'NULL'; + } else { + if ( $mode == LIST_AND || $mode == LIST_OR || $mode == LIST_SET ) { + $list .= "$field = "; + } + if ( $mode == LIST_NAMES ) { + $list .= $value; + } + else { + $list .= $this->addQuotesSmart( $table, $field, $value ); + } + } + } + return $list; + } + + /** + * Construct a LIMIT query with optional offset + * This is used for query pages + * $sql string SQL query we will append the limit too + * $limit integer the SQL limit + * $offset integer the SQL offset (default false) + */ + public function limitResult($sql, $limit, $offset=false) { + if( !is_numeric($limit) ) { + throw new DBUnexpectedError( $this, "Invalid non-numeric limit passed to limitResult()\n" ); + } + if( $offset ) { + wfDebug("Offset parameter not supported in limitResult()\n"); + } + // TODO implement proper offset handling + // idea: get all the rows between 0 and offset, advance cursor to offset + return "$sql FETCH FIRST $limit ROWS ONLY "; + } + + /** + * Handle reserved keyword replacement in table names + * @return + * @param $name Object + */ + public function tableName( $name ) { + # Replace reserved words with better ones + switch( $name ) { + case 'user': + return 'mwuser'; + case 'text': + return 'pagecontent'; + default: + return $name; + } + } + + /** + * Generates a timestamp in an insertable format + * @return string timestamp value + * @param timestamp $ts + */ + public function timestamp( $ts=0 ) { + // TS_MW cannot be easily distinguished from an integer + return wfTimestamp(TS_DB2,$ts); + } + + /** + * Return the next in a sequence, save the value for retrieval via insertId() + * @param string seqName Name of a defined sequence in the database + * @return next value in that sequence + */ + public function nextSequenceValue( $seqName ) { + $safeseq = preg_replace( "/'/", "''", $seqName ); + $res = $this->query( "VALUES NEXTVAL FOR $safeseq" ); + $row = $this->fetchRow( $res ); + $this->mInsertId = $row[0]; + $this->freeResult( $res ); + return $this->mInsertId; + } + + /** + * This must be called after nextSequenceVal + * @return Last sequence value used as a primary key + */ + public function insertId() { + return $this->mInsertId; + } + + /** + * INSERT wrapper, inserts an array into a table + * + * $args may be a single associative array, or an array of these with numeric keys, + * for multi-row insert + * + * @param array $table String: Name of the table to insert to. + * @param array $args Array: Items to insert into the table. + * @param array $fname String: Name of the function, for profiling + * @param mixed $options String or Array. Valid options: IGNORE + * + * @return bool Success of insert operation. IGNORE always returns true. + */ + public function insert( $table, $args, $fname = 'DatabaseIbm_db2::insert', $options = array() ) { + wfDebug("DB2::insert($table)\n"); + if ( !count( $args ) ) { + return true; + } + + $table = $this->tableName( $table ); + + if ( !is_array( $options ) ) + $options = array( $options ); + + if ( isset( $args[0] ) && is_array( $args[0] ) ) { + } + else { + $args = array($args); + } + $keys = array_keys( $args[0] ); + + // If IGNORE is set, we use savepoints to emulate mysql's behavior + $ignore = in_array( 'IGNORE', $options ) ? 'mw' : ''; + + // Cache autocommit value at the start + $oldautocommit = db2_autocommit($this->mConn); + + // If we are not in a transaction, we need to be for savepoint trickery + $didbegin = 0; + if (! $this->mTrxLevel) { + $this->begin(); + $didbegin = 1; + } + if ( $ignore ) { + $olde = error_reporting( 0 ); + // For future use, we may want to track the number of actual inserts + // Right now, insert (all writes) simply return true/false + $numrowsinserted = 0; + } + + $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES '; + + if ( !$ignore ) { + $first = true; + foreach ( $args as $row ) { + if ( $first ) { + $first = false; + } else { + $sql .= ','; + } + $sql .= '(' . $this->makeListSmart( $table, $row ) . ')'; + } + $res = (bool)$this->query( $sql, $fname, $ignore ); + } + else { + $res = true; + $origsql = $sql; + foreach ( $args as $row ) { + $tempsql = $origsql; + $tempsql .= '(' . $this->makeListSmart( $table, $row ) . ')'; + + if ( $ignore ) { + db2_exec($this->mConn, "SAVEPOINT $ignore"); + } + + $tempres = (bool)$this->query( $tempsql, $fname, $ignore ); + + if ( $ignore ) { + $bar = db2_stmt_error(); + if ($bar != false) { + db2_exec( $this->mConn, "ROLLBACK TO SAVEPOINT $ignore" ); + } + else { + db2_exec( $this->mConn, "RELEASE SAVEPOINT $ignore" ); + $numrowsinserted++; + } + } + + // If any of them fail, we fail overall for this function call + // Note that this will be ignored if IGNORE is set + if (! $tempres) + $res = false; + } + } + + if ($didbegin) { + $this->commit(); + } + // if autocommit used to be on, it's ok to commit everything + else if ($oldautocommit) + { + $this->commit(); + } + + if ( $ignore ) { + $olde = error_reporting( $olde ); + // Set the affected row count for the whole operation + $this->mAffectedRows = $numrowsinserted; + + // IGNORE always returns true + return true; + } + + return $res; + } + + /** + * UPDATE wrapper, takes a condition array and a SET array + * + * @param string $table The table to UPDATE + * @param array $values An array of values to SET + * @param array $conds An array of conditions (WHERE). Use '*' to update all rows. + * @param string $fname The Class::Function calling this function + * (for the log) + * @param array $options An array of UPDATE options, can be one or + * more of IGNORE, LOW_PRIORITY + * @return bool + */ + function update( $table, $values, $conds, $fname = 'Database::update', $options = array() ) { + $table = $this->tableName( $table ); + $opts = $this->makeUpdateOptions( $options ); + $sql = "UPDATE $opts $table SET " . $this->makeListSmart( $table, $values, LIST_SET ); + if ( $conds != '*' ) { + $sql .= " WHERE " . $this->makeListSmart( $table, $conds, LIST_AND ); + } + return $this->query( $sql, $fname ); + } + + /** + * DELETE query wrapper + * + * Use $conds == "*" to delete all rows + */ + function delete( $table, $conds, $fname = 'Database::delete' ) { + if ( !$conds ) { + throw new DBUnexpectedError( $this, 'Database::delete() called with no conditions' ); + } + $table = $this->tableName( $table ); + $sql = "DELETE FROM $table"; + if ( $conds != '*' ) { + $sql .= ' WHERE ' . $this->makeListSmart( $table, $conds, LIST_AND ); + } + return $this->query( $sql, $fname ); + } + + /** + * Returns the number of rows affected by the last query or 0 + * @return int the number of rows affected by the last query + */ + public function affectedRows() { + if ( !is_null( $this->mAffectedRows ) ) { + // Forced result for simulated queries + return $this->mAffectedRows; + } + if( empty( $this->mLastResult ) ) + return 0; + return db2_num_rows( $this->mLastResult ); + } + + /** + * USE INDEX clause + * DB2 doesn't have them and returns "" + * @param sting $index + */ + public function useIndexClause( $index ) { + return ""; + } + + /** + * Simulates REPLACE with a DELETE followed by INSERT + * @param $table Object + * @param array $uniqueIndexes array consisting of indexes and arrays of indexes + * @param array $rows Rows to insert + * @param string $fname Name of the function for profiling + * @return nothing + */ + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabaseIbm_db2::replace' ) { + $table = $this->tableName( $table ); + + if (count($rows)==0) { + return; + } + + # Single row case + if ( !is_array( reset( $rows ) ) ) { + $rows = array( $rows ); + } + + foreach( $rows as $row ) { + # Delete rows which collide + if ( $uniqueIndexes ) { + $sql = "DELETE FROM $table WHERE "; + $first = true; + foreach ( $uniqueIndexes as $index ) { + if ( $first ) { + $first = false; + $sql .= "("; + } else { + $sql .= ') OR ('; + } + if ( is_array( $index ) ) { + $first2 = true; + foreach ( $index as $col ) { + if ( $first2 ) { + $first2 = false; + } else { + $sql .= ' AND '; + } + $sql .= $col.'=' . $this->addQuotes( $row[$col] ); + } + } else { + $sql .= $index.'=' . $this->addQuotes( $row[$index] ); + } + } + $sql .= ')'; + $this->query( $sql, $fname ); + } + + # Now insert the row + $sql = "INSERT INTO $table (" . $this->makeList( array_keys( $row ), LIST_NAMES ) .') VALUES (' . + $this->makeList( $row, LIST_COMMA ) . ')'; + $this->query( $sql, $fname ); + } + } + + /** + * Returns the number of rows in the result set + * Has to be called right after the corresponding select query + * @param Object $res result set + * @return int number of rows + */ + public function numRows( $res ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + if ( $this->mNumRows ) { + return $this->mNumRows; + } + else { + return 0; + } + } + + /** + * Moves the row pointer of the result set + * @param Object $res result set + * @param int $row row number + * @return success or failure + */ + public function dataSeek( $res, $row ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + return db2_fetch_row( $res, $row ); + } + + ### + # Fix notices in Block.php + ### + + /** + * Frees memory associated with a statement resource + * @param Object $res Statement resource to free + * @return bool success or failure + */ + public function freeResult( $res ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + if ( !@db2_free_result( $res ) ) { + throw new DBUnexpectedError($this, "Unable to free DB2 result\n" ); + } + } + + /** + * Returns the number of columns in a resource + * @param Object $res Statement resource + * @return Number of fields/columns in resource + */ + public function numFields( $res ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + return db2_num_fields( $res ); + } + + /** + * Returns the nth column name + * @param Object $res Statement resource + * @param int $n Index of field or column + * @return string name of nth column + */ + public function fieldName( $res, $n ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + return db2_field_name( $res, $n ); + } + + /** + * SELECT wrapper + * + * @param mixed $table Array or string, table name(s) (prefix auto-added) + * @param mixed $vars Array or string, field name(s) to be retrieved + * @param mixed $conds Array or string, condition(s) for WHERE + * @param string $fname Calling function name (use __METHOD__) for logs/profiling + * @param array $options Associative array of options (e.g. array('GROUP BY' => 'page_title')), + * see Database::makeSelectOptions code for list of supported stuff + * @param array $join_conds Associative array of table join conditions (optional) + * (e.g. array( 'page' => array('LEFT JOIN','page_latest=rev_id') ) + * @return mixed Database result resource (feed to Database::fetchObject or whatever), or false on failure + */ + public function select( $table, $vars, $conds='', $fname = 'DatabaseIbm_db2::select', $options = array(), $join_conds = array() ) + { + $res = parent::select( $table, $vars, $conds, $fname, $options, $join_conds ); + + // We must adjust for offset + if ( isset( $options['LIMIT'] ) ) { + if ( isset ($options['OFFSET'] ) ) { + $limit = $options['LIMIT']; + $offset = $options['OFFSET']; + } + } + + + // DB2 does not have a proper num_rows() function yet, so we must emulate it + // DB2 9.5.3/9.5.4 and the corresponding ibm_db2 driver will introduce a working one + // Yay! + + // we want the count + $vars2 = array('count(*) as num_rows'); + // respecting just the limit option + $options2 = array(); + if ( isset( $options['LIMIT'] ) ) $options2['LIMIT'] = $options['LIMIT']; + // but don't try to emulate for GROUP BY + if ( isset( $options['GROUP BY'] ) ) return $res; + + $res2 = parent::select( $table, $vars2, $conds, $fname, $options2, $join_conds ); + $obj = $this->fetchObject($res2); + $this->mNumRows = $obj->num_rows; + + wfDebug("DatabaseIbm_db2::select: There are $this->mNumRows rows.\n"); + + return $res; + } + + /** + * Handles ordering, grouping, and having options ('GROUP BY' => colname) + * Has limited support for per-column options (colnum => 'DISTINCT') + * + * @private + * + * @param array $options an associative array of options to be turned into + * an SQL query, valid keys are listed in the function. + * @return array + */ + function makeSelectOptions( $options ) { + $preLimitTail = $postLimitTail = ''; + $startOpts = ''; + + $noKeyOptions = array(); + foreach ( $options as $key => $option ) { + if ( is_numeric( $key ) ) { + $noKeyOptions[$option] = true; + } + } + + if ( isset( $options['GROUP BY'] ) ) $preLimitTail .= " GROUP BY {$options['GROUP BY']}"; + if ( isset( $options['HAVING'] ) ) $preLimitTail .= " HAVING {$options['HAVING']}"; + if ( isset( $options['ORDER BY'] ) ) $preLimitTail .= " ORDER BY {$options['ORDER BY']}"; + + if ( isset( $noKeyOptions['DISTINCT'] ) || isset( $noKeyOptions['DISTINCTROW'] ) ) $startOpts .= 'DISTINCT'; + + return array( $startOpts, '', $preLimitTail, $postLimitTail ); + } + + /** + * Returns link to IBM DB2 free download + * @return string wikitext of a link to the server software's web site + */ + public function getSoftwareLink() { + return "[http://www.ibm.com/software/data/db2/express/?s_cmp=ECDDWW01&s_tact=MediaWiki IBM DB2]"; + } + + /** + * Does nothing + * @param object $db + * @return bool true + */ + public function selectDB( $db ) { + return true; + } + + /** + * Returns an SQL expression for a simple conditional. + * Uses CASE on DB2 + * + * @param string $cond SQL expression which will result in a boolean value + * @param string $trueVal SQL expression to return if true + * @param string $falseVal SQL expression to return if false + * @return string SQL fragment + */ + public function conditional( $cond, $trueVal, $falseVal ) { + return " (CASE WHEN $cond THEN $trueVal ELSE $falseVal END) "; + } + + ### + # Fix search crash + ### + /** + * Get search engine class. All subclasses of this + * need to implement this if they wish to use searching. + * + * @return string + */ + public function getSearchEngine() { + return "SearchIBM_DB2"; + } + + ### + # Tuesday the 14th of October, 2008 + ### + /** + * Did the last database access fail because of deadlock? + * @return bool + */ + public function wasDeadlock() { + // get SQLSTATE + $err = $this->lastErrno(); + switch($err) { + case '40001': // sql0911n, Deadlock or timeout, rollback + case '57011': // sql0904n, Resource unavailable, no rollback + case '57033': // sql0913n, Deadlock or timeout, no rollback + wfDebug("In a deadlock because of SQLSTATE $err"); + return true; + } + return false; + } + + /** + * Ping the server and try to reconnect if it there is no connection + * The connection may be closed and reopened while this happens + * @return bool whether the connection exists + */ + public function ping() { + // db2_ping() doesn't exist + // Emulate + $this->close(); + if ($this->mCataloged == NULL) { + return false; + } + else if ($this->mCataloged) { + $this->mConn = $this->openCataloged($this->mDBName, $this->mUser, $this->mPassword); + } + else if (!$this->mCataloged) { + $this->mConn = $this->openUncataloged($this->mDBName, $this->mUser, $this->mPassword, $this->mServer, $this->mPort); + } + return false; + } + ###################################### + # Unimplemented and not applicable + ###################################### + /** + * Not implemented + * @return string '' + * @deprecated + */ + public function getStatus( $which ) { wfDebug('Not implemented for DB2: getStatus()'); return ''; } + /** + * Not implemented + * @deprecated + */ + public function setTimeout( $timeout ) { wfDebug('Not implemented for DB2: setTimeout()'); } + /** + * Not implemented + * TODO + * @return bool true + */ + public function lock( $lockName, $method ) { wfDebug('Not implemented for DB2: lock()'); return true; } + /** + * Not implemented + * TODO + * @return bool true + */ + public function unlock( $lockName, $method ) { wfDebug('Not implemented for DB2: unlock()'); return true; } + /** + * Not implemented + * @deprecated + */ + public function setFakeSlaveLag( $lag ) { wfDebug('Not implemented for DB2: setFakeSlaveLag()'); } + /** + * Not implemented + * @deprecated + */ + public function setFakeMaster( $enabled ) { wfDebug('Not implemented for DB2: setFakeMaster()'); } + /** + * Not implemented + * @return string $sql + * @deprecated + */ + public function limitResultForUpdate($sql, $num) { return $sql; } + /** + * No such option + * @return string '' + * @deprecated + */ + public function lowPriorityOption() { return ''; } + + ###################################### + # Reflection + ###################################### + + /** + * Query whether a given column exists in the mediawiki schema + * @param string $table name of the table + * @param string $field name of the column + * @param string $fname function name for logging and profiling + */ + public function fieldExists( $table, $field, $fname = 'DatabaseIbm_db2::fieldExists' ) { + $table = $this->tableName( $table ); + $schema = $this->mSchema; + $etable = preg_replace("/'/", "''", $table); + $eschema = preg_replace("/'/", "''", $schema); + $ecol = preg_replace("/'/", "''", $field); + $sql = <<<SQL +SELECT 1 as fieldexists +FROM sysibm.syscolumns sc +WHERE sc.name='$ecol' AND sc.tbname='$etable' AND sc.tbcreator='$eschema' +SQL; + $res = $this->query( $sql, $fname ); + $count = $res ? $this->numRows($res) : 0; + if ($res) + $this->freeResult( $res ); + return $count; + } + + /** + * Returns information about an index + * If errors are explicitly ignored, returns NULL on failure + * @param string $table table name + * @param string $index index name + * @param string + * @return object query row in object form + */ + public function indexInfo( $table, $index, $fname = 'DatabaseIbm_db2::indexExists' ) { + $table = $this->tableName( $table ); + $sql = <<<SQL +SELECT name as indexname +FROM sysibm.sysindexes si +WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema' +SQL; + $res = $this->query( $sql, $fname ); + if ( !$res ) { + return NULL; + } + $row = $this->fetchObject( $res ); + if ($row != NULL) return $row; + else return false; + } + + /** + * Returns an information object on a table column + * @param string $table table name + * @param string $field column name + * @return IBM_DB2Field + */ + public function fieldInfo( $table, $field ) { + return IBM_DB2Field::fromText($this, $table, $field); + } + + /** + * db2_field_type() wrapper + * @param Object $res Result of executed statement + * @param mixed $index number or name of the column + * @return string column type + */ + public function fieldType( $res, $index ) { + if ( $res instanceof ResultWrapper ) { + $res = $res->result; + } + return db2_field_type( $res, $index ); + } + + /** + * Verifies that an index was created as unique + * @param string $table table name + * @param string $index index name + * @param string $fnam function name for profiling + * @return bool + */ + public function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { + $table = $this->tableName( $table ); + $sql = <<<SQL +SELECT si.name as indexname +FROM sysibm.sysindexes si +WHERE si.name='$index' AND si.tbname='$table' AND sc.tbcreator='$this->mSchema' +AND si.uniquerule IN ('U', 'P') +SQL; + $res = $this->query( $sql, $fname ); + if ( !$res ) { + return null; + } + if ($this->fetchObject( $res )) { + return true; + } + return false; + + } + + /** + * Returns the size of a text field, or -1 for "unlimited" + * @param string $table table name + * @param string $field column name + * @return int length or -1 for unlimited + */ + public function textFieldSize( $table, $field ) { + $table = $this->tableName( $table ); + $sql = <<<SQL +SELECT length as size +FROM sysibm.syscolumns sc +WHERE sc.name='$field' AND sc.tbname='$table' AND sc.tbcreator='$this->mSchema' +SQL; + $res = $this->query($sql); + $row = $this->fetchObject($res); + $size = $row->size; + $this->freeResult( $res ); + return $size; + } + + /** + * DELETE where the condition is a join + * @param string $delTable deleting from this table + * @param string $joinTable using data from this table + * @param string $delVar variable in deleteable table + * @param string $joinVar variable in data table + * @param array $conds conditionals for join table + * @param string $fname function name for profiling + */ + public function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "DatabaseIbm_db2::deleteJoin" ) { + if ( !$conds ) { + throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); + } + + $delTable = $this->tableName( $delTable ); + $joinTable = $this->tableName( $joinTable ); + $sql = "DELETE FROM $delTable WHERE $delVar IN (SELECT $joinVar FROM $joinTable "; + if ( $conds != '*' ) { + $sql .= 'WHERE ' . $this->makeList( $conds, LIST_AND ); + } + $sql .= ')'; + + $this->query( $sql, $fname ); + } + + /** + * Estimate rows in dataset + * Returns estimated count, based on COUNT(*) output + * Takes same arguments as Database::select() + * @param string $table table name + * @param array $vars unused + * @param array $conds filters on the table + * @param string $fname function name for profiling + * @param array $options options for select + * @return int row count + */ + public function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + $rows = 0; + $res = $this->select ($table, 'COUNT(*) as mwrowcount', $conds, $fname, $options ); + if ($res) { + $row = $this->fetchRow($res); + $rows = (isset($row['mwrowcount'])) ? $row['mwrowcount'] : 0; + } + $this->freeResult($res); + return $rows; + } + + /** + * Description is left as an exercise for the reader + * @param mixed $b data to be encoded + * @return IBM_DB2Blob + */ + public function encodeBlob($b) { + return new IBM_DB2Blob($b); + } + + /** + * Description is left as an exercise for the reader + * @param IBM_DB2Blob $b data to be decoded + * @return mixed + */ + public function decodeBlob($b) { + return $b->getData(); + } + + /** + * Convert into a list of string being concatenated + * @param array $stringList strings that need to be joined together by the SQL engine + * @return string joined by the concatenation operator + */ + public function buildConcat( $stringList ) { + // || is equivalent to CONCAT + // Sample query: VALUES 'foo' CONCAT 'bar' CONCAT 'baz' + return implode( ' || ', $stringList ); + } + + /** + * Generates the SQL required to convert a DB2 timestamp into a Unix epoch + * @param string $column name of timestamp column + * @return string SQL code + */ + public function extractUnixEpoch( $column ) { + // TODO + // see SpecialAncientpages + } +} +?>
\ No newline at end of file diff --git a/includes/db/DatabasePostgres.php b/includes/db/DatabasePostgres.php index 16a74b53..c940ad09 100644 --- a/includes/db/DatabasePostgres.php +++ b/includes/db/DatabasePostgres.php @@ -174,9 +174,11 @@ class DatabasePostgres extends Database { global $wgCommandLineMode; ## If called from the command-line (e.g. importDump), only show errors if ($wgCommandLineMode) { - $this->doQuery("SET client_min_messages = 'ERROR'"); + $this->doQuery( "SET client_min_messages = 'ERROR'" ); } + $this->doQuery( "SET client_encoding='UTF8'" ); + global $wgDBmwschema, $wgDBts2schema; if (isset( $wgDBmwschema ) && isset( $wgDBts2schema ) && $wgDBmwschema !== 'mediawiki' @@ -185,7 +187,7 @@ class DatabasePostgres extends Database { ) { $safeschema = $this->quote_ident($wgDBmwschema); $safeschema2 = $this->quote_ident($wgDBts2schema); - $this->doQuery("SET search_path = $safeschema, $wgDBts2schema, public"); + $this->doQuery( "SET search_path = $safeschema, $wgDBts2schema, public" ); } return $this->mConn; @@ -255,7 +257,7 @@ class DatabasePostgres extends Database { print "<li>Database \"" . htmlspecialchars( $wgDBname ) . "\" already exists, skipping database creation.</li>"; } else { - if ($perms < 2) { + if ($perms < 1) { print "<li>ERROR: the user \"" . htmlspecialchars( $wgDBsuperuser ) . "\" cannot create databases. "; print 'Please use a different Postgres user.</li>'; dieout('</ul>'); @@ -687,7 +689,7 @@ class DatabasePostgres extends Database { * Takes same arguments as Database::select() */ - function estimateRowCount( $table, $vars='*', $conds='', $fname = 'Database::estimateRowCount', $options = array() ) { + function estimateRowCount( $table, $vars='*', $conds='', $fname = 'DatabasePostgres::estimateRowCount', $options = array() ) { $options['EXPLAIN'] = true; $res = $this->select( $table, $vars, $conds, $fname, $options ); $rows = -1; @@ -707,23 +709,25 @@ class DatabasePostgres extends Database { * Returns information about an index * If errors are explicitly ignored, returns NULL on failure */ - function indexInfo( $table, $index, $fname = 'Database::indexExists' ) { + function indexInfo( $table, $index, $fname = 'DatabasePostgres::indexInfo' ) { $sql = "SELECT indexname FROM pg_indexes WHERE tablename='$table'"; $res = $this->query( $sql, $fname ); if ( !$res ) { return NULL; } while ( $row = $this->fetchObject( $res ) ) { - if ( $row->indexname == $index ) { + if ( $row->indexname == $this->indexName( $index ) ) { return $row; } } return false; } - function indexUnique ($table, $index, $fname = 'Database::indexUnique' ) { + function indexUnique ($table, $index, $fname = 'DatabasePostgres::indexUnique' ) { $sql = "SELECT indexname FROM pg_indexes WHERE tablename='{$table}'". - " AND indexdef LIKE 'CREATE UNIQUE%({$index})'"; + " AND indexdef LIKE 'CREATE UNIQUE%(" . + $this->strencode( $this->indexName( $index ) ) . + ")'"; $res = $this->query( $sql, $fname ); if ( !$res ) return NULL; @@ -921,7 +925,7 @@ class DatabasePostgres extends Database { # It may be more efficient to leave off unique indexes which are unlikely to collide. # However if you do this, you run the risk of encountering errors which wouldn't have # occurred in MySQL - function replace( $table, $uniqueIndexes, $rows, $fname = 'Database::replace' ) { + function replace( $table, $uniqueIndexes, $rows, $fname = 'DatabasePostgres::replace' ) { $table = $this->tableName( $table ); if (count($rows)==0) { @@ -971,7 +975,7 @@ class DatabasePostgres extends Database { } # DELETE where the condition is a join - function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = "Database::deleteJoin" ) { + function deleteJoin( $delTable, $joinTable, $delVar, $joinVar, $conds, $fname = 'DatabasePostgres::deleteJoin' ) { if ( !$conds ) { throw new DBUnexpectedError($this, 'Database::deleteJoin() called with empty $conds' ); } @@ -1072,12 +1076,15 @@ class DatabasePostgres extends Database { */ function getServerVersion() { $versionInfo = pg_version( $this->mConn ); - if ( isset( $versionInfo['server'] ) ) { + if ( version_compare( $versionInfo['client'], '7.4.0', 'lt' ) ) { + // Old client, abort install + $this->numeric_version = '7.3 or earlier'; + } elseif ( isset( $versionInfo['server'] ) ) { + // Normal client $this->numeric_version = $versionInfo['server']; } else { - // There's no way to identify the precise version before 7.4, but - // it doesn't matter anyway since we're just going to give an error. - $this->numeric_version = '7.3 or earlier'; + // Bug 16937: broken pgsql extension from PHP<5.3 + $this->numeric_version = pg_parameter_status( $this->mConn, 'server_version' ); } return $this->numeric_version; } @@ -1088,12 +1095,12 @@ class DatabasePostgres extends Database { */ function relationExists( $table, $types, $schema = false ) { global $wgDBmwschema; - if (!is_array($types)) - $types = array($types); - if (! $schema ) + if ( !is_array( $types ) ) + $types = array( $types ); + if ( !$schema ) $schema = $wgDBmwschema; - $etable = $this->addQuotes($table); - $eschema = $this->addQuotes($schema); + $etable = $this->addQuotes( $table ); + $eschema = $this->addQuotes( $schema ); $SQL = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n " . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema " . "AND c.relkind IN ('" . implode("','", $types) . "')"; @@ -1108,15 +1115,15 @@ class DatabasePostgres extends Database { * For backward compatibility, this function checks both tables and * views. */ - function tableExists ($table, $schema = false) { - return $this->relationExists($table, array('r', 'v'), $schema); + function tableExists( $table, $schema = false ) { + return $this->relationExists( $table, array( 'r', 'v' ), $schema ); } - function sequenceExists ($sequence, $schema = false) { - return $this->relationExists($sequence, 'S', $schema); + function sequenceExists( $sequence, $schema = false ) { + return $this->relationExists( $sequence, 'S', $schema ); } - function triggerExists($table, $trigger) { + function triggerExists( $table, $trigger ) { global $wgDBmwschema; $q = <<<END @@ -1132,20 +1139,20 @@ END; if (!$res) return NULL; $rows = $res->numRows(); - $this->freeResult($res); + $this->freeResult( $res ); return $rows; } - function ruleExists($table, $rule) { + function ruleExists( $table, $rule ) { global $wgDBmwschema; $exists = $this->selectField("pg_rules", "rulename", array( "rulename" => $rule, "tablename" => $table, - "schemaname" => $wgDBmwschema)); + "schemaname" => $wgDBmwschema ) ); return $exists === $rule; } - function constraintExists($table, $constraint) { + function constraintExists( $table, $constraint ) { global $wgDBmwschema; $SQL = sprintf("SELECT 1 FROM information_schema.table_constraints ". "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s", @@ -1224,7 +1231,7 @@ END; } /* Not even sure why this is used in the main codebase... */ - function limitResultForUpdate($sql, $num) { + function limitResultForUpdate( $sql, $num ) { return $sql; } diff --git a/includes/db/DatabaseSqlite.php b/includes/db/DatabaseSqlite.php index dfc506cc..7a595697 100644 --- a/includes/db/DatabaseSqlite.php +++ b/includes/db/DatabaseSqlite.php @@ -15,6 +15,7 @@ class DatabaseSqlite extends Database { var $mAffectedRows; var $mLastResult; var $mDatabaseFile; + var $mName; /** * Constructor @@ -26,6 +27,7 @@ class DatabaseSqlite extends Database { $this->mFailFunction = $failFunction; $this->mFlags = $flags; $this->mDatabaseFile = "$wgSQLiteDataDir/$dbName.sqlite"; + $this->mName = $dbName; $this->open($server, $user, $password, $dbName); } @@ -89,8 +91,9 @@ class DatabaseSqlite extends Database { */ function doQuery($sql) { $res = $this->mConn->query($sql); - if ($res === false) $this->reportQueryError($this->lastError(),$this->lastErrno(),$sql,__FUNCTION__); - else { + if ($res === false) { + return false; + } else { $r = $res instanceof ResultWrapper ? $res->result : $res; $this->mAffectedRows = $r->rowCount(); $res = new ResultWrapper($this,$r->fetchAll()); @@ -98,11 +101,11 @@ class DatabaseSqlite extends Database { return $res; } - function freeResult(&$res) { + function freeResult($res) { if ($res instanceof ResultWrapper) $res->result = NULL; else $res = NULL; } - function fetchObject(&$res) { + function fetchObject($res) { if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res; $cur = current($r); if (is_array($cur)) { @@ -114,7 +117,7 @@ class DatabaseSqlite extends Database { return false; } - function fetchRow(&$res) { + function fetchRow($res) { if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res; $cur = current($r); if (is_array($cur)) { @@ -127,17 +130,17 @@ class DatabaseSqlite extends Database { /** * The PDO::Statement class implements the array interface so count() will work */ - function numRows(&$res) { + function numRows($res) { $r = $res instanceof ResultWrapper ? $res->result : $res; return count($r); } - function numFields(&$res) { + function numFields($res) { $r = $res instanceof ResultWrapper ? $res->result : $res; return is_array($r) ? count($r[0]) : 0; } - function fieldName(&$res,$n) { + function fieldName($res,$n) { $r = $res instanceof ResultWrapper ? $res->result : $res; if (is_array($r)) { $keys = array_keys($r[0]); @@ -154,13 +157,20 @@ class DatabaseSqlite extends Database { } /** + * Index names have DB scope + */ + function indexName( $index ) { + return $index; + } + + /** * This must be called after nextSequenceVal */ function insertId() { return $this->mConn->lastInsertId(); } - function dataSeek(&$res,$row) { + function dataSeek($res,$row) { if ($res instanceof ResultWrapper) $r =& $res->result; else $r =& $res; reset($r); if ($row > 0) for ($i = 0; $i < $row; $i++) next($r); @@ -173,8 +183,12 @@ class DatabaseSqlite extends Database { } function lastErrno() { - if (!is_object($this->mConn)) return "Cannot return last error, no db connection"; - return $this->mConn->errorCode(); + if (!is_object($this->mConn)) { + return "Cannot return last error, no db connection"; + } else { + $info = $this->mConn->errorInfo(); + return $info[1]; + } } function affectedRows() { @@ -183,14 +197,43 @@ class DatabaseSqlite extends Database { /** * Returns information about an index + * Returns false if the index does not exist * - if errors are explicitly ignored, returns NULL on failure */ function indexInfo($table, $index, $fname = 'Database::indexExists') { - return false; + $sql = 'PRAGMA index_info(' . $this->addQuotes( $this->indexName( $index ) ) . ')'; + $res = $this->query( $sql, $fname ); + if ( !$res ) { + return null; + } + if ( $res->numRows() == 0 ) { + return false; + } + $info = array(); + foreach ( $res as $row ) { + $info[] = $row->name; + } + return $info; } function indexUnique($table, $index, $fname = 'Database::indexUnique') { - return false; + $row = $this->selectRow( 'sqlite_master', '*', + array( + 'type' => 'index', + 'name' => $this->indexName( $index ), + ), $fname ); + if ( !$row || !isset( $row->sql ) ) { + return null; + } + + // $row->sql will be of the form CREATE [UNIQUE] INDEX ... + $indexPos = strpos( $row->sql, 'INDEX' ); + if ( $indexPos === false ) { + return null; + } + $firstPart = substr( $row->sql, 0, $indexPos ); + $options = explode( ' ', $firstPart ); + return in_array( 'UNIQUE', $options ); } /** @@ -228,7 +271,10 @@ class DatabaseSqlite extends Database { return ''; } - # Returns the size of a text field, or -1 for "unlimited" + /** + * Returns the size of a text field, or -1 for "unlimited" + * In SQLite this is SQLITE_MAX_LENGTH, by default 1GB. No way to query it though. + */ function textFieldSize($table, $field) { return -1; } @@ -252,6 +298,10 @@ class DatabaseSqlite extends Database { return $this->lastErrno() == SQLITE_BUSY; } + function wasErrorReissuable() { + return $this->lastErrno() == SQLITE_SCHEMA; + } + /** * @return string wikitext of a link to the server software's web site */ @@ -265,38 +315,53 @@ class DatabaseSqlite extends Database { function getServerVersion() { global $wgContLang; $ver = $this->mConn->getAttribute(PDO::ATTR_SERVER_VERSION); - $size = $wgContLang->formatSize(filesize($this->mDatabaseFile)); - $file = basename($this->mDatabaseFile); - return $ver." ($file: $size)"; + return $ver; } /** * Query whether a given column exists in the mediawiki schema */ - function fieldExists($table, $field) { return true; } + function fieldExists($table, $field, $fname = '') { + $info = $this->fieldInfo( $table, $field ); + return (bool)$info; + } - function fieldInfo($table, $field) { return SQLiteField::fromText($this, $table, $field); } + /** + * Get information about a given field + * Returns false if the field does not exist. + */ + function fieldInfo($table, $field) { + $tableName = $this->tableName( $table ); + $sql = 'PRAGMA table_info(' . $this->addQuotes( $tableName ) . ')'; + $res = $this->query( $sql, __METHOD__ ); + foreach ( $res as $row ) { + if ( $row->name == $field ) { + return new SQLiteField( $row, $tableName ); + } + } + return false; + } - function begin() { + function begin( $fname = '' ) { if ($this->mTrxLevel == 1) $this->commit(); $this->mConn->beginTransaction(); $this->mTrxLevel = 1; } - function commit() { + function commit( $fname = '' ) { if ($this->mTrxLevel == 0) return; $this->mConn->commit(); $this->mTrxLevel = 0; } - function rollback() { + function rollback( $fname = '' ) { if ($this->mTrxLevel == 0) return; $this->mConn->rollBack(); $this->mTrxLevel = 0; } function limitResultForUpdate($sql, $num) { - return $sql; + return $this->limitResult( $sql, $num ); } function strencode($s) { @@ -325,17 +390,25 @@ class DatabaseSqlite extends Database { function quote_ident($s) { return $s; } /** - * For now, does nothing + * Not possible in SQLite + * We have ATTACH_DATABASE but that requires database selectors before the + * table names and in any case is really a different concept to MySQL's USE */ - function selectDB($db) { return true; } + function selectDB($db) { + if ( $db != $this->mName ) { + throw new MWException( 'selectDB is not implemented in SQLite' ); + } + } /** * not done */ public function setTimeout($timeout) { return; } + /** + * No-op for a non-networked database + */ function ping() { - wfDebug("Function ping() not written for SQLite yet"); return true; } @@ -353,39 +426,16 @@ class DatabaseSqlite extends Database { public function setup_database() { global $IP,$wgSQLiteDataDir,$wgDBTableOptions; $wgDBTableOptions = ''; - $mysql_tmpl = "$IP/maintenance/tables.sql"; - $mysql_iw = "$IP/maintenance/interwiki.sql"; - $sqlite_tmpl = "$IP/maintenance/sqlite/tables.sql"; - - # Make an SQLite template file if it doesn't exist (based on the same one MySQL uses to create a new wiki db) - if (!file_exists($sqlite_tmpl)) { - $sql = file_get_contents($mysql_tmpl); - $sql = preg_replace('/^\s*--.*?$/m','',$sql); # strip comments - $sql = preg_replace('/^\s*(UNIQUE)?\s*(PRIMARY)?\s*KEY.+?$/m','',$sql); - $sql = preg_replace('/^\s*(UNIQUE )?INDEX.+?$/m','',$sql); # These indexes should be created with a CREATE INDEX query - $sql = preg_replace('/^\s*FULLTEXT.+?$/m','',$sql); # Full text indexes - $sql = preg_replace('/ENUM\(.+?\)/','TEXT',$sql); # Make ENUM's into TEXT's - $sql = preg_replace('/binary\(\d+\)/','BLOB',$sql); - $sql = preg_replace('/(TYPE|MAX_ROWS|AVG_ROW_LENGTH)=\w+/','',$sql); - $sql = preg_replace('/,\s*\)/s',')',$sql); # removing previous items may leave a trailing comma - $sql = str_replace('binary','',$sql); - $sql = str_replace('auto_increment','PRIMARY KEY AUTOINCREMENT',$sql); - $sql = str_replace(' unsigned','',$sql); - $sql = str_replace(' int ',' INTEGER ',$sql); - $sql = str_replace('NOT NULL','',$sql); - - # Tidy up and write file - $sql = preg_replace('/^\s*^/m','',$sql); # Remove empty lines - $sql = preg_replace('/;$/m',";\n",$sql); # Separate each statement with an empty line - file_put_contents($sqlite_tmpl,$sql); - } - # Parse the SQLite template replacing inline variables such as /*$wgDBprefix*/ - $err = $this->sourceFile($sqlite_tmpl); - if ($err !== true) $this->reportQueryError($err,0,$sql,__FUNCTION__); + # Process common MySQL/SQLite table definitions + $err = $this->sourceFile( "$IP/maintenance/tables.sql" ); + if ($err !== true) { + $this->reportQueryError($err,0,$sql,__FUNCTION__); + exit( 1 ); + } # Use DatabasePostgres's code to populate interwiki from MySQL template - $f = fopen($mysql_iw,'r'); + $f = fopen("$IP/maintenance/interwiki.sql",'r'); if ($f == false) dieout("<li>Could not find the interwiki.sql file"); $sql = "INSERT INTO interwiki(iw_prefix,iw_url,iw_local) VALUES "; while (!feof($f)) { @@ -418,22 +468,80 @@ class DatabaseSqlite extends Database { $function = array_shift( $args ); return call_user_func_array( $function, $args ); } -} + + protected function replaceVars( $s ) { + $s = parent::replaceVars( $s ); + if ( preg_match( '/^\s*CREATE TABLE/i', $s ) ) { + // CREATE TABLE hacks to allow schema file sharing with MySQL + + // binary/varbinary column type -> blob + $s = preg_replace( '/\b(var)?binary(\(\d+\))/i', 'blob\1', $s ); + // no such thing as unsigned + $s = preg_replace( '/\bunsigned\b/i', '', $s ); + // INT -> INTEGER for primary keys + $s = preg_replacE( '/\bint\b/i', 'integer', $s ); + // No ENUM type + $s = preg_replace( '/enum\([^)]*\)/i', 'blob', $s ); + // binary collation type -> nothing + $s = preg_replace( '/\bbinary\b/i', '', $s ); + // auto_increment -> autoincrement + $s = preg_replace( '/\bauto_increment\b/i', 'autoincrement', $s ); + // No explicit options + $s = preg_replace( '/\)[^)]*$/', ')', $s ); + } elseif ( preg_match( '/^\s*CREATE (\s*(?:UNIQUE|FULLTEXT)\s+)?INDEX/i', $s ) ) { + // No truncated indexes + $s = preg_replace( '/\(\d+\)/', '', $s ); + // No FULLTEXT + $s = preg_replace( '/\bfulltext\b/i', '', $s ); + } + return $s; + } + +} // end DatabaseSqlite class /** * @ingroup Database */ -class SQLiteField extends MySQLField { +class SQLiteField { + private $info, $tableName; + function __construct( $info, $tableName ) { + $this->info = $info; + $this->tableName = $tableName; + } - function __construct() { + function name() { + return $this->info->name; } - static function fromText($db, $table, $field) { - $n = new SQLiteField; - $n->name = $field; - $n->tablename = $table; - return $n; + function tableName() { + return $this->tableName; } -} // end DatabaseSqlite class + function defaultValue() { + if ( is_string( $this->info->dflt_value ) ) { + // Typically quoted + if ( preg_match( '/^\'(.*)\'$', $this->info->dflt_value ) ) { + return str_replace( "''", "'", $this->info->dflt_value ); + } + } + return $this->info->dflt_value; + } + + function maxLength() { + return -1; + } + + function nullable() { + // SQLite dynamic types are always nullable + return true; + } + + # isKey(), isMultipleKey() not implemented, MySQL-specific concept. + # Suggest removal from base class [TS] + + function type() { + return $this->info->type; + } + +} // end SQLiteField diff --git a/includes/db/LBFactory.php b/includes/db/LBFactory.php index 256875d7..3876d71f 100644 --- a/includes/db/LBFactory.php +++ b/includes/db/LBFactory.php @@ -236,15 +236,25 @@ class ChronologyProtector { * @param LoadBalancer $lb */ function shutdownLB( $lb ) { - if ( session_id() != '' && $lb->getServerCount() > 1 ) { - $masterName = $lb->getServerName( 0 ); - if ( !isset( $this->shutdownPos[$masterName] ) ) { - $pos = $lb->getMasterPos(); - $info = $lb->parentInfo(); - wfDebug( __METHOD__.": LB " . $info['id'] . " has master pos $pos\n" ); - $this->shutdownPos[$masterName] = $pos; - } + // Don't start a session, don't bother with non-replicated setups + if ( strval( session_id() ) == '' || $lb->getServerCount() <= 1 ) { + return; + } + $masterName = $lb->getServerName( 0 ); + if ( isset( $this->shutdownPos[$masterName] ) ) { + // Already done + return; + } + // Only save the position if writes have been done on the connection + $db = $lb->getAnyOpenConnection( 0 ); + $info = $lb->parentInfo(); + if ( !$db || !$db->doneWrites() ) { + wfDebug( __METHOD__.": LB {$info['id']}, no writes done\n" ); + return; } + $pos = $db->getMasterPos(); + wfDebug( __METHOD__.": LB {$info['id']} has master pos $pos\n" ); + $this->shutdownPos[$masterName] = $pos; } /** diff --git a/includes/db/LoadBalancer.php b/includes/db/LoadBalancer.php index f847fe22..0b8ef05a 100644 --- a/includes/db/LoadBalancer.php +++ b/includes/db/LoadBalancer.php @@ -824,7 +824,7 @@ class LoadBalancer { continue; } foreach ( $conns2[$masterIndex] as $conn ) { - if ( $conn->lastQuery() != '' ) { + if ( $conn->doneWrites() ) { $conn->commit(); } } diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index b30ff190..aa48f9f3 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -27,7 +27,10 @@ class DifferenceEngine { var $mOldRev, $mNewRev; var $mRevisionsLoaded = false; // Have the revisions been loaded var $mTextLoaded = 0; // How many text blobs have been loaded, 0, 1 or 2? + var $mCacheHit = false; // Was the diff fetched from cache? var $htmldiff; + + protected $unhide = false; /**#@-*/ /** @@ -38,23 +41,20 @@ class DifferenceEngine { * @param $rcid Integer: ??? FIXME (default 0) * @param $refreshCache boolean If set, refreshes the diff cache * @param $htmldiff boolean If set, output using HTMLDiff instead of raw wikicode diff + * @param $unhide boolean If set, allow viewing deleted revs */ - function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0, $refreshCache = false , $htmldiff = false) { + function __construct( $titleObj = null, $old = 0, $new = 0, $rcid = 0, $refreshCache = false , $htmldiff = false, $unhide = false ) { $this->mTitle = $titleObj; wfDebug("DifferenceEngine old '$old' new '$new' rcid '$rcid'\n"); if ( 'prev' === $new ) { # Show diff between revision $old and the previous one. # Get previous one from DB. - # $this->mNewid = intval($old); - $this->mOldid = $this->mTitle->getPreviousRevisionID( $this->mNewid ); - } elseif ( 'next' === $new ) { - # Show diff between revision $old and the previous one. - # Get previous one from DB. - # + # Show diff between revision $old and the next one. + # Get next one from DB. $this->mOldid = intval($old); $this->mNewid = $this->mTitle->getNextRevisionID( $this->mOldid ); if ( false === $this->mNewid ) { @@ -62,19 +62,32 @@ class DifferenceEngine { # revision is cur, which is "0". $this->mNewid = 0; } - } else { $this->mOldid = intval($old); $this->mNewid = intval($new); + wfRunHooks( 'NewDifferenceEngine', array(&$titleObj, &$this->mOldid, &$this->mNewid, $old, $new) ); } $this->mRcidMarkPatrolled = intval($rcid); # force it to be an integer $this->mRefreshCache = $refreshCache; $this->htmldiff = $htmldiff; + $this->unhide = $unhide; } function getTitle() { return $this->mTitle; } + + function wasCacheHit() { + return $this->mCacheHit; + } + + function getOldid() { + return $this->mOldid; + } + + function getNewid() { + return $this->mNewid; + } function showDiffPage( $diffOnly = false ) { global $wgUser, $wgOut, $wgUseExternalEditor, $wgUseRCPatrol, $wgEnableHtmlDiff; @@ -111,7 +124,7 @@ CONTROL; } $wgOut->setArticleFlag( false ); - if ( ! $this->loadRevisionData() ) { + if ( !$this->loadRevisionData() ) { $t = $this->mTitle->getPrefixedText(); $d = wfMsgExt( 'missingarticle-diff', array( 'escape' ), $this->mOldid, $this->mNewid ); $wgOut->setPagetitle( wfMsg( 'errorpagetitle' ) ); @@ -171,22 +184,26 @@ CONTROL; // If we've been given an explicit change identifier, use it; saves time if( $this->mRcidMarkPatrolled ) { $rcid = $this->mRcidMarkPatrolled; + $rc = RecentChange::newFromId( $rcid ); + // Already patrolled? + $rcid = is_object($rc) && !$rc->getAttribute('rc_patrolled') ? $rcid : 0; } else { // Look for an unpatrolled change corresponding to this diff $db = wfGetDB( DB_SLAVE ); $change = RecentChange::newFromConds( - array( - // Add redundant user,timestamp condition so we can use the existing index - 'rc_user_text' => $this->mNewRev->getUserText( Revision::FOR_THIS_USER ), - 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ), - 'rc_this_oldid' => $this->mNewid, - 'rc_last_oldid' => $this->mOldid, - 'rc_patrolled' => 0 - ), - __METHOD__ + array( + // Add redundant user,timestamp condition so we can use the existing index + 'rc_user_text' => $this->mNewRev->getRawUserText(), + 'rc_timestamp' => $db->timestamp( $this->mNewRev->getTimestamp() ), + 'rc_this_oldid' => $this->mNewid, + 'rc_last_oldid' => $this->mOldid, + 'rc_patrolled' => 0 + ), + __METHOD__ ); if( $change instanceof RecentChange ) { $rcid = $change->mAttribs['rc_id']; + $this->mRcidMarkPatrolled = $rcid; } else { // None found $rcid = 0; @@ -194,11 +211,8 @@ CONTROL; } // Build the link if( $rcid ) { - $patrol = ' <span class="patrollink">[' . $sk->makeKnownLinkObj( - $this->mTitle, - wfMsgHtml( 'markaspatrolleddiff' ), - "action=markpatrolled&rcid={$rcid}" - ) . ']</span>'; + $patrol = ' <span class="patrollink">[' . $sk->makeKnownLinkObj( $this->mTitle, + wfMsgHtml( 'markaspatrolleddiff' ), "action=markpatrolled&rcid={$rcid}" ) . ']</span>'; } else { $patrol = ''; } @@ -206,81 +220,100 @@ CONTROL; $patrol = ''; } + $diffOnlyArg = ''; + # Carry over 'diffonly' param via navigation links + if( $diffOnly != $wgUser->getBoolOption('diffonly') ) { + $diffOnlyArg = '&diffonly='.$diffOnly; + } $htmldiffarg = $this->htmlDiffArgument(); + # Make "previous revision link" $prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'previousdiff' ), - 'diff=prev&oldid='.$this->mOldid.$htmldiffarg, '', '', 'id="differences-prevlink"' ); - if ( $this->mNewRev->isCurrent() ) { + "diff=prev&oldid={$this->mOldid}{$htmldiffarg}{$diffOnlyArg}", '', '', 'id="differences-prevlink"' ); + # Make "next revision link" + if( $this->mNewRev->isCurrent() ) { $nextlink = ' '; } else { $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), - 'diff=next&oldid='.$this->mNewid.$htmldiffarg, '', '', 'id="differences-nextlink"' ); + "diff=next&oldid={$this->mNewid}{$htmldiffarg}{$diffOnlyArg}", '', '', 'id="differences-nextlink"' ); } $oldminor = ''; $newminor = ''; - if ($this->mOldRev->mMinorEdit == 1) { + if( $this->mOldRev->isMinor() ) { $oldminor = Xml::span( wfMsg( 'minoreditletter' ), 'minor' ) . ' '; } - - if ($this->mNewRev->mMinorEdit == 1) { + if( $this->mNewRev->isMinor() ) { $newminor = Xml::span( wfMsg( 'minoreditletter' ), 'minor' ) . ' '; } $rdel = ''; $ldel = ''; if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); if( !$this->mOldRev->userCan( Revision::DELETED_RESTRICTED ) ) { // If revision was hidden from sysops - $ldel = wfMsgHtml( 'rev-delundel' ); + $ldel = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml( 'rev-delundel' ).')' ); } else { - $ldel = $sk->makeKnownLinkObj( $revdel, - wfMsgHtml( 'rev-delundel' ), - 'target=' . urlencode( $this->mOldRev->mTitle->getPrefixedDbkey() ) . - '&oldid=' . urlencode( $this->mOldRev->getId() ) ); - // Bolden oversighted content - if( $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ) - $ldel = "<strong>$ldel</strong>"; + $query = array( 'target' => $this->mOldRev->mTitle->getPrefixedDbkey(), + 'oldid' => $this->mOldRev->getId() + ); + $ldel = $sk->revDeleteLink( $query, $this->mOldRev->isDeleted( Revision::DELETED_RESTRICTED ) ); } - $ldel = " <tt>(<small>$ldel</small>)</tt> "; + $ldel = " $ldel "; // We don't currently handle well changing the top revision's settings if( $this->mNewRev->isCurrent() ) { - // If revision was hidden from sysops - $rdel = wfMsgHtml( 'rev-delundel' ); + $rdel = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml( 'rev-delundel' ).')' ); } else if( !$this->mNewRev->userCan( Revision::DELETED_RESTRICTED ) ) { // If revision was hidden from sysops - $rdel = wfMsgHtml( 'rev-delundel' ); + $rdel = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml( 'rev-delundel' ).')' ); } else { - $rdel = $sk->makeKnownLinkObj( $revdel, - wfMsgHtml( 'rev-delundel' ), - 'target=' . urlencode( $this->mNewRev->mTitle->getPrefixedDbkey() ) . - '&oldid=' . urlencode( $this->mNewRev->getId() ) ); - // Bolden oversighted content - if( $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ) - $rdel = "<strong>$rdel</strong>"; + $query = array( 'target' => $this->mNewRev->mTitle->getPrefixedDbkey(), + 'oldid' => $this->mNewRev->getId() + ); + $rdel = $sk->revDeleteLink( $query, $this->mNewRev->isDeleted( Revision::DELETED_RESTRICTED ) ); } - $rdel = " <tt>(<small>$rdel</small>)</tt> "; + $rdel = " $rdel "; } $oldHeader = '<div id="mw-diff-otitle1"><strong>'.$this->mOldtitle.'</strong></div>' . - '<div id="mw-diff-otitle2">' . $sk->revUserTools( $this->mOldRev, true ) . "</div>" . - '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, true ) . $ldel . "</div>" . + '<div id="mw-diff-otitle2">' . $sk->revUserTools( $this->mOldRev, !$this->unhide ) . "</div>" . + '<div id="mw-diff-otitle3">' . $oldminor . $sk->revComment( $this->mOldRev, !$diffOnly, !$this->unhide ).$ldel."</div>" . '<div id="mw-diff-otitle4">' . $prevlink .'</div>'; $newHeader = '<div id="mw-diff-ntitle1"><strong>'.$this->mNewtitle.'</strong></div>' . - '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, true ) . " $rollback</div>" . - '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly, true ) . $rdel . "</div>" . + '<div id="mw-diff-ntitle2">' . $sk->revUserTools( $this->mNewRev, !$this->unhide ) . " $rollback</div>" . + '<div id="mw-diff-ntitle3">' . $newminor . $sk->revComment( $this->mNewRev, !$diffOnly, !$this->unhide ).$rdel."</div>" . '<div id="mw-diff-ntitle4">' . $nextlink . $patrol . '</div>'; - if( $wgEnableHtmlDiff && $this->htmldiff) { + # Check if this user can see the revisions + $allowed = $this->mOldRev->userCan(Revision::DELETED_TEXT) + && $this->mNewRev->userCan(Revision::DELETED_TEXT); + $deleted = $this->mOldRev->isDeleted(Revision::DELETED_TEXT) + || $this->mNewRev->isDeleted(Revision::DELETED_TEXT); + # Output the diff if allowed... + if( $deleted && (!$this->unhide || !$allowed) ) { + $this->showDiffStyle(); + $multi = $this->getMultiNotice(); + $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); + if( !$allowed ) { + # Give explanation for why revision is not visible + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", + array( 'rev-deleted-no-diff' ) ); + } else { + # Give explanation and add a link to view the diff... + $link = $this->mTitle->getFullUrl( "diff={$this->mNewid}&oldid={$this->mOldid}". + '&unhide=1&token='.urlencode( $wgUser->editToken($this->mNewid) ) ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", + array( 'rev-deleted-unhide-diff', $link ) ); + } + } else if( $wgEnableHtmlDiff && $this->htmldiff ) { $multi = $this->getMultiNotice(); $wgOut->addHTML('<div class="diff-switchtype">'.$sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'wikicodecomparison' ), - 'diff='.$this->mNewid.'&oldid='.$this->mOldid.'&htmldiff=0', '', '', 'id="differences-switchtype"' ).'</div>'); + 'diff='.$this->mNewid.'&oldid='.$this->mOldid.'&htmldiff=0', '', '', 'id="differences-switchtype"' ).'</div>'); $wgOut->addHTML( $this->addHeader( '', $oldHeader, $newHeader, $multi ) ); $this->renderHtmlDiff(); } else { - if($wgEnableHtmlDiff){ + if( $wgEnableHtmlDiff ) { $wgOut->addHTML('<div class="diff-switchtype">'.$sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'visualcomparison' ), - 'diff='.$this->mNewid.'&oldid='.$this->mOldid.'&htmldiff=1', '', '', 'id="differences-switchtype"' ).'</div>'); + 'diff='.$this->mNewid.'&oldid='.$this->mOldid.'&htmldiff=1', '', '', 'id="differences-switchtype"' ).'</div>'); } $this->showDiff( $oldHeader, $newHeader ); if( !$diffOnly ) { @@ -294,15 +327,15 @@ CONTROL; * Show the new revision of the page. */ function renderNewRevision() { - global $wgOut; + global $wgOut, $wgUser; wfProfileIn( __METHOD__ ); $wgOut->addHTML( "<hr /><h2>{$this->mPagetitle}</h2>\n" ); - #add deleted rev tag if needed + # Add deleted rev tag if needed if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); } if( !$this->mNewRev->isCurrent() ) { @@ -314,9 +347,8 @@ CONTROL; $wgOut->setRevisionId( $this->mNewRev->getId() ); } - if ($this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage()) { + if( $this->mTitle->isCssJsSubpage() || $this->mTitle->isCssOrJsPage() ) { // Stolen from Article::view --AG 2007-10-11 - // Give hooks a chance to customise the output if( wfRunHooks( 'ShowRawCssJs', array( $this->mNewtext, $this->mTitle, $wgOut ) ) ) { // Wrap the whole lot in a <pre> and don't parse @@ -326,12 +358,22 @@ CONTROL; $wgOut->addHTML( htmlspecialchars( $this->mNewtext ) ); $wgOut->addHTML( "\n</pre>\n" ); } - } else - $wgOut->addWikiTextTidy( $this->mNewtext ); + } else { + $wgOut->addWikiTextTidy( $this->mNewtext ); + } - if( !$this->mNewRev->isCurrent() ) { + if( is_object( $this->mNewRev ) && !$this->mNewRev->isCurrent() ) { $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); } + # Add redundant patrol link on bottom... + if( $this->mRcidMarkPatrolled && $this->mTitle->quickUserCan('patrol') ) { + $sk = $wgUser->getSkin(); + $wgOut->addHTML( + "<div class='patrollink'>[" . $sk->makeKnownLinkObj( $this->mTitle, + wfMsgHtml( 'markaspatrolleddiff' ), "action=markpatrolled&rcid={$this->mRcidMarkPatrolled}" ) . + ']</div>' + ); + } wfProfileOut( __METHOD__ ); } @@ -346,9 +388,9 @@ CONTROL; $wgOut->addHTML( '<h2>'.wfMsgHtml( 'visual-comparison' )."</h2>\n" ); #add deleted rev tag if needed if( !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-permission' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); } else if( $this->mNewRev->isDeleted(Revision::DELETED_TEXT) ) { - $wgOut->addWikiMsg( 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); } if( !$this->mNewRev->isCurrent() ) { @@ -435,11 +477,15 @@ CONTROL; # $sk = $wgUser->getSkin(); - $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), 'diff=next&oldid='.$this->mNewid.$this->htmlDiffArgument(), '', '', 'id="differences-nextlink"' ); - $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\"><strong>{$this->mOldtitle}</strong><br />" . - $sk->revUserTools( $this->mNewRev ) . "<br />" . - $sk->revComment( $this->mNewRev ) . "<br />" . - $nextlink . "</div>\n"; + $next = $this->mTitle->getNextRevisionID( $this->mNewid ); + if( !$next ) { + $nextlink = ''; + } else { + $nextlink = '<br/>' . $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'nextdiff' ), + 'diff=next&oldid=' . $this->mNewid.$this->htmlDiffArgument(), '', '', 'id="differences-nextlink"' ); + } + $header = "<div class=\"firstrevisionheader\" style=\"text-align: center\">" . + $sk->revUserTools( $this->mNewRev ) . "<br/>" . $sk->revComment( $this->mNewRev ) . $nextlink . "</div>\n"; $wgOut->addHTML( $header ); @@ -515,11 +561,16 @@ CONTROL; function getDiffBody() { global $wgMemc; wfProfileIn( __METHOD__ ); + $this->mCacheHit = true; // Check if the diff should be hidden from this user + if ( !$this->loadRevisionData() ) + return ''; if ( $this->mOldRev && !$this->mOldRev->userCan(Revision::DELETED_TEXT) ) { return ''; } else if ( $this->mNewRev && !$this->mNewRev->userCan(Revision::DELETED_TEXT) ) { return ''; + } else if ( $this->mOldRev && $this->mNewRev && $this->mOldRev->getID() == $this->mNewRev->getID() ) { + return ''; } // Cacheable? $key = false; @@ -537,6 +588,7 @@ CONTROL; } } // don't try to load but save the result } + $this->mCacheHit = false; // Loadtext is permission safe, this just clears out the diff if ( !$this->loadText() ) { @@ -669,7 +721,7 @@ CONTROL; function localiseLineNumbersCb( $matches ) { global $wgLang; - return wfMsgExt( 'lineno', array( 'parseinline' ), $wgLang->formatNum( $matches[1] ) ); + return wfMsgExt( 'lineno', array (), $wgLang->formatNum( $matches[1] ) ); } @@ -728,6 +780,7 @@ CONTROL; $this->mOldtext = $oldText; $this->mNewtext = $newText; $this->mTextLoaded = 2; + $this->mRevisionsLoaded = true; } /** @@ -751,10 +804,10 @@ CONTROL; // Load the new revision object $this->mNewRev = $this->mNewid - ? Revision::newFromId( $this->mNewid ) - : Revision::newFromTitle( $this->mTitle ); + ? Revision::newFromId( $this->mNewid ) + : Revision::newFromTitle( $this->mTitle ); if( !$this->mNewRev instanceof Revision ) - return false; + return false; // Update the new revision ID in case it was 0 (makes life easier doing UI stuff) $this->mNewid = $this->mNewRev->getId(); @@ -876,7 +929,7 @@ CONTROL; if ( !$this->loadRevisionData() ) { return false; } - $this->mNewtext = $this->mNewRev->getText(); + $this->mNewtext = $this->mNewRev->getText( Revision::FOR_THIS_USER ); return true; } diff --git a/includes/diff/HTMLDiff.php b/includes/diff/HTMLDiff.php index 0698059f..df9f4eb8 100644 --- a/includes/diff/HTMLDiff.php +++ b/includes/diff/HTMLDiff.php @@ -839,6 +839,10 @@ class NoContentTagToString extends TagToString { } public function getRemovedDescription(ChangeText $txt) { + $tagDescription = wfMsgExt('diff-' . $this->node->qName, 'parseinline' ); + if( wfEmptyMsg( 'diff-' . $this->node->qName, $tagDescription ) ){ + $tagDescription = "<" . $this->node->qName . ">"; + } $txt->addHtml( wfMsgExt('diff-changedfrom', 'parseinline', $tagDescription ) ); $this->addAttributes($txt, $this->node->attributes); $txt->addHtml('.'); diff --git a/includes/filerepo/ArchivedFile.php b/includes/filerepo/ArchivedFile.php index 3919cfbc..68c93b8f 100644 --- a/includes/filerepo/ArchivedFile.php +++ b/includes/filerepo/ArchivedFile.php @@ -74,14 +74,16 @@ class ArchivedFile } $conds = array(); - if ($this->id>0) + if( $this->id > 0 ) $conds['fa_id'] = $this->id; - if ($this->key) - $conds['fa_storage_key'] = $this->key; - if ($this->title) + if( $this->key ) { + $conds['fa_storage_group'] = $this->group; + $conds['fa_storage_key'] = $this->key; + } + if( $this->title ) $conds['fa_name'] = $this->title->getDBkey(); - if (!count($conds)) + if( !count($conds)) throw new MWException( "No specific information for retrieving archived file" ); if( !$this->title || $this->title->getNamespace() == NS_FILE ) { diff --git a/includes/filerepo/File.php b/includes/filerepo/File.php index 4f0990af..523a1c09 100644 --- a/includes/filerepo/File.php +++ b/includes/filerepo/File.php @@ -586,7 +586,7 @@ abstract class File { } while (false); wfProfileOut( __METHOD__ ); - return $thumb; + return is_object( $thumb ) ? $thumb : false; } /** @@ -865,22 +865,23 @@ abstract class File { * * @deprecated Use HTMLCacheUpdate, this function uses too much memory */ - function getLinksTo( $options = '' ) { + function getLinksTo( $options = array() ) { wfProfileIn( __METHOD__ ); // Note: use local DB not repo DB, we want to know local links - if ( $options ) { + if ( count( $options ) > 0 ) { $db = wfGetDB( DB_MASTER ); } else { $db = wfGetDB( DB_SLAVE ); } $linkCache = LinkCache::singleton(); - list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' ); $encName = $db->addQuotes( $this->getName() ); - $sql = "SELECT page_namespace,page_title,page_id,page_len,page_is_redirect, - FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options"; - $res = $db->query( $sql, __METHOD__ ); + $res = $db->select( array( 'page', 'imagelinks'), + array( 'page_namespace', 'page_title', 'page_id', 'page_len', 'page_is_redirect' ), + array( 'page_id' => 'il_from', 'il_to' => $encName ), + __METHOD__, + $options ); $retVal = array(); if ( $db->numRows( $res ) ) { @@ -950,7 +951,7 @@ abstract class File { */ function wasDeleted() { $title = $this->getTitle(); - return $title && $title->isDeleted() > 0; + return $title && $title->isDeletedQuick(); } /** @@ -1068,15 +1069,16 @@ abstract class File { * Get the HTML text of the description page, if available */ function getDescriptionText() { - global $wgMemc; + global $wgMemc, $wgContLang; if ( !$this->repo->fetchDescription ) { return false; } - $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName() ); + $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() ); if ( $renderUrl ) { if ( $this->repo->descriptionCacheExpiry > 0 ) { wfDebug("Attempting to get the description from cache..."); - $key = wfMemcKey( 'RemoteFileDescription', 'url', md5($renderUrl) ); + $key = wfMemcKey( 'RemoteFileDescription', 'url', $wgContLang->getCode(), + $this->getName() ); $obj = $wgMemc->get($key); if ($obj) { wfDebug("success!\n"); @@ -1086,7 +1088,9 @@ abstract class File { } wfDebug( "Fetching shared description from $renderUrl\n" ); $res = Http::get( $renderUrl ); - if ( $res && $this->repo->descriptionCacheExpiry > 0 ) $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry ); + if ( $res && $this->repo->descriptionCacheExpiry > 0 ) { + $wgMemc->set( $key, $res, $this->repo->descriptionCacheExpiry ); + } return $res; } else { return false; diff --git a/includes/filerepo/FileRepo.php b/includes/filerepo/FileRepo.php index 5beac732..face1614 100644 --- a/includes/filerepo/FileRepo.php +++ b/includes/filerepo/FileRepo.php @@ -294,16 +294,22 @@ abstract class FileRepo { * MediaWiki this means action=render. This should only be called by the * repository's file class, since it may return invalid results. User code * should use File::getDescriptionText(). + * @param string $name Name of image to fetch + * @param string $lang Language to fetch it in, if any. */ - function getDescriptionRenderUrl( $name ) { + function getDescriptionRenderUrl( $name, $lang = null ) { + $query = 'action=render'; + if ( !is_null( $lang ) ) { + $query .= '&uselang=' . $lang; + } if ( isset( $this->scriptDirUrl ) ) { return $this->scriptDirUrl . '/index.php?title=' . wfUrlencode( 'Image:' . $name ) . - '&action=render'; + "&$query"; } else { $descUrl = $this->getDescriptionUrl( $name ); if ( $descUrl ) { - return wfAppendQuery( $descUrl, 'action=render' ); + return wfAppendQuery( $descUrl, $query ); } else { return false; } @@ -512,24 +518,84 @@ abstract class FileRepo { /** * Checks if there is a redirect named as $title - * STUB * * @param Title $title Title of image */ function checkRedirect( $title ) { - return false; + global $wgMemc; + + if( is_string( $title ) ) { + $title = Title::newFromTitle( $title ); + } + if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) { + $title = Title::makeTitle( NS_FILE, $title->getText() ); + } + + $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) ); + $cachedValue = $wgMemc->get( $memcKey ); + if( $cachedValue ) { + return Title::newFromDbKey( $cachedValue ); + } elseif( $cachedValue == ' ' ) { # FIXME: ugly hack, but BagOStuff caching seems to be weird and return false if !cachedValue, not only if it doesn't exist + return false; + } + + $id = $this->getArticleID( $title ); + if( !$id ) { + $wgMemc->set( $memcKey, " ", 9000 ); + return false; + } + $dbr = $this->getSlaveDB(); + $row = $dbr->selectRow( + 'redirect', + array( 'rd_title', 'rd_namespace' ), + array( 'rd_from' => $id ), + __METHOD__ + ); + + if( $row ) $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title ); + $wgMemc->set( $memcKey, ($row ? $targetTitle->getPrefixedDBkey() : " "), 9000 ); + if( !$row ) { + return false; + } + return $targetTitle; } /** * Invalidates image redirect cache related to that image - * STUB * * @param Title $title Title of image - */ + */ function invalidateImageRedirect( $title ) { + global $wgMemc; + $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) ); + $wgMemc->delete( $memcKey ); } function findBySha1( $hash ) { return array(); } + + /** + * Get the human-readable name of the repo. + * @return string + */ + public function getDisplayName() { + // We don't name our own repo, return nothing + if ( $this->name == 'local' ) { + return null; + } + $repoName = wfMsg( 'shared-repo-name-' . $this->name ); + if ( !wfEmptyMsg( 'shared-repo-name-' . $this->name, $repoName ) ) { + return $repoName; + } + return wfMsg( 'shared-repo' ); + } + + function getSlaveDB() { + return wfGetDB( DB_SLAVE ); + } + + function getMasterDB() { + return wfGetDB( DB_MASTER ); + } } diff --git a/includes/filerepo/ForeignAPIFile.php b/includes/filerepo/ForeignAPIFile.php index d9fb85d0..03498fb1 100644 --- a/includes/filerepo/ForeignAPIFile.php +++ b/includes/filerepo/ForeignAPIFile.php @@ -59,7 +59,21 @@ class ForeignAPIFile extends File { } public function getMetadata() { - return serialize( (array)@$this->mInfo['metadata'] ); + if ( isset( $this->mInfo['metadata'] ) ) { + return serialize( self::parseMetadata( $this->mInfo['metadata'] ) ); + } + return null; + } + + public static function parseMetadata( $metadata ) { + if( !is_array( $metadata ) ) { + return $metadata; + } + $ret = array(); + foreach( $metadata as $meta ) { + $ret[ $meta['name'] ] = self::parseMetadata( $meta['value'] ); + } + return $ret; } public function getSize() { @@ -87,11 +101,11 @@ class ForeignAPIFile extends File { } function getMimeType() { - if( empty( $info['mime'] ) ) { + if( !isset( $this->mInfo['mime'] ) ) { $magic = MimeMagic::singleton(); - $info['mime'] = $magic->guessTypesForExtension( $this->getExtension() ); + $this->mInfo['mime'] = $magic->guessTypesForExtension( $this->getExtension() ); } - return $info['mime']; + return $this->mInfo['mime']; } /// @fixme May guess wrong on file types that can be eg audio or video @@ -146,8 +160,8 @@ class ForeignAPIFile extends File { } function purgeDescriptionPage() { - global $wgMemc; - $url = $this->repo->getDescriptionRenderUrl( $this->getName() ); + global $wgMemc, $wgContLang; + $url = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgContLang->getCode() ); $key = wfMemcKey( 'RemoteFileDescription', 'url', md5($url) ); $wgMemc->delete( $key ); } diff --git a/includes/filerepo/ForeignAPIRepo.php b/includes/filerepo/ForeignAPIRepo.php index 6fc9c465..e63e4a6b 100644 --- a/includes/filerepo/ForeignAPIRepo.php +++ b/includes/filerepo/ForeignAPIRepo.php @@ -30,6 +30,17 @@ class ForeignAPIRepo extends FileRepo { $this->scriptDirUrl = dirname( $this->mApiBase ); } } + + /** + * Per docs in FileRepo, this needs to return false if we don't support versioned + * files. Well, we don't. + */ + function newFile( $title, $time = false ) { + if ( $time ) { + return false; + } + return parent::newFile( $title, $time ); + } /** * No-ops @@ -109,7 +120,7 @@ class ForeignAPIRepo extends FileRepo { $ret = array(); if ( isset( $results['query']['allimages'] ) ) { foreach ( $results['query']['allimages'] as $img ) { - $ret[] = new ForeignAPIFile( Title::makeTitle( NS_IMAGE, $img['name'] ), $this, $img ); + $ret[] = new ForeignAPIFile( Title::makeTitle( NS_FILE, $img['name'] ), $this, $img ); } } return $ret; diff --git a/includes/filerepo/ForeignDBFile.php b/includes/filerepo/ForeignDBFile.php index 5fb432c8..8fe6f921 100644 --- a/includes/filerepo/ForeignDBFile.php +++ b/includes/filerepo/ForeignDBFile.php @@ -20,7 +20,7 @@ class ForeignDBFile extends LocalFile { } function getCacheKey() { - if ( $this->repo->hasSharedCache ) { + if ( $this->repo->hasSharedCache() ) { $hashedName = md5($this->name); return wfForeignMemcKey( $this->repo->dbName, $this->repo->tablePrefix, 'file', $hashedName ); diff --git a/includes/filerepo/LocalFile.php b/includes/filerepo/LocalFile.php index 6fd6de72..b997d75f 100644 --- a/includes/filerepo/LocalFile.php +++ b/includes/filerepo/LocalFile.php @@ -1760,12 +1760,12 @@ class LocalFileMoveBatch { $oldName = $row->oi_archive_name; $bits = explode( '!', $oldName, 2 ); if( count( $bits ) != 2 ) { - wfDebug( 'Invalid old file name: ' . $oldName ); + wfDebug( "Invalid old file name: $oldName \n" ); continue; } list( $timestamp, $filename ) = $bits; if( $this->oldName != $filename ) { - wfDebug( 'Invalid old file name:' . $oldName ); + wfDebug( "Invalid old file name: $oldName \n" ); continue; } $this->oldCount++; diff --git a/includes/filerepo/LocalRepo.php b/includes/filerepo/LocalRepo.php index 5eb1a11c..1ec1b9a6 100644 --- a/includes/filerepo/LocalRepo.php +++ b/includes/filerepo/LocalRepo.php @@ -10,14 +10,6 @@ class LocalRepo extends FSRepo { var $fileFromRowFactory = array( 'LocalFile', 'newFromRow' ); var $oldFileFromRowFactory = array( 'OldLocalFile', 'newFromRow' ); - function getSlaveDB() { - return wfGetDB( DB_SLAVE ); - } - - function getMasterDB() { - return wfGetDB( DB_MASTER ); - } - function getMemcKey( $key ) { return wfWikiID( $this->getSlaveDB() ) . ":{$key}"; } @@ -101,50 +93,7 @@ class LocalRepo extends FSRepo { return $id; } - function checkRedirect( $title ) { - global $wgMemc; - - if( is_string( $title ) ) { - $title = Title::newFromTitle( $title ); - } - if( $title instanceof Title && $title->getNamespace() == NS_MEDIA ) { - $title = Title::makeTitle( NS_FILE, $title->getText() ); - } - - $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) ); - $cachedValue = $wgMemc->get( $memcKey ); - if( $cachedValue ) { - return Title::newFromDbKey( $cachedValue ); - } elseif( $cachedValue == ' ' ) { # FIXME: ugly hack, but BagOStuff caching seems to be weird and return false if !cachedValue, not only if it doesn't exist - return false; - } - $id = $this->getArticleID( $title ); - if( !$id ) { - $wgMemc->set( $memcKey, " ", 9000 ); - return false; - } - $dbr = $this->getSlaveDB(); - $row = $dbr->selectRow( - 'redirect', - array( 'rd_title', 'rd_namespace' ), - array( 'rd_from' => $id ), - __METHOD__ - ); - - if( $row ) $targetTitle = Title::makeTitle( $row->rd_namespace, $row->rd_title ); - $wgMemc->set( $memcKey, ($row ? $targetTitle->getPrefixedDBkey() : " "), 9000 ); - if( !$row ) { - return false; - } - return $targetTitle; - } - - function invalidateImageRedirect( $title ) { - global $wgMemc; - $memcKey = $this->getMemcKey( "image_redirect:" . md5( $title->getPrefixedDBkey() ) ); - $wgMemc->delete( $memcKey ); - } function findBySha1( $hash ) { $dbr = $this->getSlaveDB(); diff --git a/includes/media/Bitmap.php b/includes/media/Bitmap.php index b949ae3d..c2f2458e 100644 --- a/includes/media/Bitmap.php +++ b/includes/media/Bitmap.php @@ -42,7 +42,7 @@ class BitmapHandler extends ImageHandler { function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) { global $wgUseImageMagick, $wgImageMagickConvertCommand, $wgImageMagickTempDir; - global $wgCustomConvertCommand; + global $wgCustomConvertCommand, $wgUseImageResize; global $wgSharpenParameter, $wgSharpenReductionThreshold; global $wgMaxAnimatedGifArea; @@ -69,6 +69,8 @@ class BitmapHandler extends ImageHandler { if ( !$dstPath ) { // No output path available, client side scaling only $scaler = 'client'; + } elseif( !$wgUseImageResize ) { + $scaler = 'client'; } elseif ( $wgUseImageMagick ) { $scaler = 'im'; } elseif ( $wgCustomConvertCommand ) { diff --git a/includes/media/Tiff.php b/includes/media/Tiff.php new file mode 100644 index 00000000..9d3fbb78 --- /dev/null +++ b/includes/media/Tiff.php @@ -0,0 +1,33 @@ +<?php +/** + * @file + * @ingroup Media + */ + +/** + * @ingroup Media + */ +class TiffHandler extends BitmapHandler { + + /** + * Conversion to PNG for inline display can be disabled here... + * Note scaling should work with ImageMagick, but may not with GD scaling. + */ + function canRender( $file ) { + global $wgTiffThumbnailType; + return (bool)$wgTiffThumbnailType; + } + + /** + * Browsers don't support TIFF inline generally... + * For inline display, we need to convert to PNG. + */ + function mustRender( $file ) { + return true; + } + + function getThumbType( $ext, $mime ) { + global $wgTiffThumbnailType; + return $wgTiffThumbnailType; + } +} diff --git a/includes/mime.types b/includes/mime.types index 2b8cb9ab..da15b5ba 100644 --- a/includes/mime.types +++ b/includes/mime.types @@ -118,3 +118,19 @@ video/x-msvideo avi video/x-ogg ogm ogg video/x-sgi-movie movie x-conference/x-cooltalk ice +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.chart-template otc +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.image-template oti +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.formula-template otf +application/vnd.oasis.opendocument.text-master odm +application/vnd.oasis.opendocument.text-web oth
\ No newline at end of file diff --git a/includes/parser/CoreParserFunctions.php b/includes/parser/CoreParserFunctions.php index a3b5189a..774e96a7 100644 --- a/includes/parser/CoreParserFunctions.php +++ b/includes/parser/CoreParserFunctions.php @@ -27,9 +27,11 @@ class CoreParserFunctions { $parser->setFunctionHook( 'fullurle', array( __CLASS__, 'fullurle' ), SFH_NO_HASH ); $parser->setFunctionHook( 'formatnum', array( __CLASS__, 'formatnum' ), SFH_NO_HASH ); $parser->setFunctionHook( 'grammar', array( __CLASS__, 'grammar' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'gender', array( __CLASS__, 'gender' ), SFH_NO_HASH ); $parser->setFunctionHook( 'plural', array( __CLASS__, 'plural' ), SFH_NO_HASH ); $parser->setFunctionHook( 'numberofpages', array( __CLASS__, 'numberofpages' ), SFH_NO_HASH ); $parser->setFunctionHook( 'numberofusers', array( __CLASS__, 'numberofusers' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'numberofactiveusers', array( __CLASS__, 'numberofactiveusers' ), SFH_NO_HASH ); $parser->setFunctionHook( 'numberofarticles', array( __CLASS__, 'numberofarticles' ), SFH_NO_HASH ); $parser->setFunctionHook( 'numberoffiles', array( __CLASS__, 'numberoffiles' ), SFH_NO_HASH ); $parser->setFunctionHook( 'numberofadmins', array( __CLASS__, 'numberofadmins' ), SFH_NO_HASH ); @@ -45,7 +47,27 @@ class CoreParserFunctions { $parser->setFunctionHook( 'filepath', array( __CLASS__, 'filepath' ), SFH_NO_HASH ); $parser->setFunctionHook( 'pagesincategory', array( __CLASS__, 'pagesincategory' ), SFH_NO_HASH ); $parser->setFunctionHook( 'pagesize', array( __CLASS__, 'pagesize' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'protectionlevel', array( __CLASS__, 'protectionlevel' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'namespace', array( __CLASS__, 'mwnamespace' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'namespacee', array( __CLASS__, 'namespacee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'talkspace', array( __CLASS__, 'talkspace' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'talkspacee', array( __CLASS__, 'talkspacee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subjectspace', array( __CLASS__, 'subjectspace' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subjectspacee', array( __CLASS__, 'subjectspacee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'pagename', array( __CLASS__, 'pagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'pagenamee', array( __CLASS__, 'pagenamee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'fullpagename', array( __CLASS__, 'fullpagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'fullpagenamee', array( __CLASS__, 'fullpagenamee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'basepagename', array( __CLASS__, 'basepagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'basepagenamee', array( __CLASS__, 'basepagenamee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subpagename', array( __CLASS__, 'subpagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subpagenamee', array( __CLASS__, 'subpagenamee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'talkpagename', array( __CLASS__, 'talkpagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'talkpagenamee', array( __CLASS__, 'talkpagenamee' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subjectpagename', array( __CLASS__, 'subjectpagename' ), SFH_NO_HASH ); + $parser->setFunctionHook( 'subjectpagenamee', array( __CLASS__, 'subjectpagenamee' ), SFH_NO_HASH ); $parser->setFunctionHook( 'tag', array( __CLASS__, 'tagObj' ), SFH_OBJECT_ARGS ); + $parser->setFunctionHook( 'formatdate', array( __CLASS__, 'formatDate' ) ); if ( $wgAllowDisplayTitle ) { $parser->setFunctionHook( 'displaytitle', array( __CLASS__, 'displaytitle' ), SFH_NO_HASH ); @@ -66,6 +88,22 @@ class CoreParserFunctions { return array( 'found' => false ); } } + + static function formatDate( $parser, $date, $defaultPref = null ) { + $df = DateFormatter::getInstance(); + + $date = trim($date); + + $pref = $parser->mOptions->getDateFormat(); + + // Specify a different default date format other than the the normal default + // iff the user has 'default' for their setting + if ($pref == 'default' && $defaultPref) + $pref = $defaultPref; + + $date = $df->reformat( $pref, $date, array('match-whole') ); + return $date; + } static function ns( $parser, $part1 = '' ) { global $wgContLang; @@ -154,6 +192,28 @@ class CoreParserFunctions { return $parser->getFunctionLang()->convertGrammar( $word, $case ); } + static function gender( $parser, $user ) { + $forms = array_slice( func_get_args(), 2); + + // default + $gender = User::getDefaultOption( 'gender' ); + + // allow prefix. + $title = Title::newFromText( $user ); + + if (is_object( $title ) && $title->getNamespace() == NS_USER) + $user = $title->getText(); + + // check parameter, or use $wgUser if in interface message + $user = User::newFromName( $user ); + if ( $user ) { + $gender = $user->getOption( 'gender' ); + } elseif ( $parser->mOptions->getInterfaceMessage() ) { + global $wgUser; + $gender = $wgUser->getOption( 'gender' ); + } + return $parser->getFunctionLang()->gender( $gender, $forms ); + } static function plural( $parser, $text = '') { $forms = array_slice( func_get_args(), 2); $text = $parser->getFunctionLang()->parseFormattedNumber( $text ); @@ -208,6 +268,9 @@ class CoreParserFunctions { static function numberofusers( $parser, $raw = null ) { return self::formatRaw( SiteStats::users(), $raw ); } + static function numberofactiveusers( $parser, $raw = null ) { + return self::formatRaw( SiteStats::activeUsers(), $raw ); + } static function numberofarticles( $parser, $raw = null ) { return self::formatRaw( SiteStats::articles(), $raw ); } @@ -230,6 +293,126 @@ class CoreParserFunctions { return self::formatRaw( SiteStats::numberingroup( strtolower( $name ) ), $raw ); } + + /** + * Given a title, return the namespace name that would be given by the + * corresponding magic word + * Note: function name changed to "mwnamespace" rather than "namespace" + * to not break PHP 5.3 + */ + static function mwnamespace( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return str_replace( '_', ' ', $t->getNsText() ); + } + static function namespacee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return wfUrlencode( $t->getNsText() ); + } + static function talkspace( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return str_replace( '_', ' ', $t->getTalkNsText() ); + } + static function talkspacee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return wfUrlencode( $t->getTalkNsText() ); + } + static function subjectspace( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return str_replace( '_', ' ', $t->getSubjectNsText() ); + } + static function subjectspacee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return wfUrlencode( $t->getSubjectNsText() ); + } + /* + * Functions to get and normalize pagenames, corresponding to the magic words + * of the same names + */ + static function pagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return wfEscapeWikiText( $t->getText() ); + } + static function pagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return $t->getPartialURL(); + } + static function fullpagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return wfEscapeWikiText( $t->getPrefixedText() ); + } + static function fullpagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return $t->getPrefixedURL(); + } + static function subpagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return $t->getSubpageText(); + } + static function subpagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return $t->getSubpageUrlForm(); + } + static function basepagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return $t->getBaseText(); + } + static function basepagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return wfUrlEncode( str_replace( ' ', '_', $t->getBaseText() ) ); + } + static function talkpagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return wfEscapeWikiText( $t->getTalkPage()->getPrefixedText() ); + } + static function talkpagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) || !$t->canTalk() ) + return ''; + return $t->getTalkPage()->getPrefixedUrl(); + } + static function subjectpagename( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return wfEscapeWikiText( $t->getSubjectPage()->getPrefixedText() ); + } + static function subjectpagenamee( $parser, $title = null ) { + $t = Title::newFromText( $title ); + if ( is_null($t) ) + return ''; + return $t->getSubjectPage()->getPrefixedUrl(); + } + /** * Return the number of pages in the given category, or 0 if it's nonexis- * tent. This is an expensive parser function and can't be called too many @@ -292,6 +475,16 @@ class CoreParserFunctions { } return self::formatRaw( $length, $raw ); } + + /** + * Returns the requested protection level for the current page + */ + static function protectionlevel( $parser, $type = '' ) { + $restrictions = $parser->mTitle->getRestrictions( strtolower( $type ) ); + # Title::getRestrictions returns an array, its possible it may have + # multiple values in the future + return implode( $restrictions, ',' ); + } static function language( $parser, $arg = '' ) { global $wgContLang; @@ -299,20 +492,38 @@ class CoreParserFunctions { return $lang != '' ? $lang : $arg; } - static function pad( $string = '', $length = 0, $char = 0, $direction = STR_PAD_RIGHT ) { - $length = min( max( $length, 0 ), 500 ); - $char = substr( $char, 0, 1 ); - return ( $string !== '' && (int)$length > 0 && strlen( trim( (string)$char ) ) > 0 ) - ? str_pad( $string, $length, (string)$char, $direction ) - : $string; + /** + * Unicode-safe str_pad with the restriction that $length is forced to be <= 500 + */ + static function pad( $string, $length, $padding = '0', $direction = STR_PAD_RIGHT ) { + $lengthOfPadding = mb_strlen( $padding ); + if ( $lengthOfPadding == 0 ) return $string; + + # The remaining length to add counts down to 0 as padding is added + $length = min( $length, 500 ) - mb_strlen( $string ); + # $finalPadding is just $padding repeated enough times so that + # mb_strlen( $string ) + mb_strlen( $finalPadding ) == $length + $finalPadding = ''; + while ( $length > 0 ) { + # If $length < $lengthofPadding, truncate $padding so we get the + # exact length desired. + $finalPadding .= mb_substr( $padding, 0, $length ); + $length -= $lengthOfPadding; + } + + if ( $direction == STR_PAD_LEFT ) { + return $finalPadding . $string; + } else { + return $string . $finalPadding; + } } - static function padleft( $parser, $string = '', $length = 0, $char = 0 ) { - return self::pad( $string, $length, $char, STR_PAD_LEFT ); + static function padleft( $parser, $string = '', $length = 0, $padding = '0' ) { + return self::pad( $string, $length, $padding, STR_PAD_LEFT ); } - static function padright( $parser, $string = '', $length = 0, $char = 0 ) { - return self::pad( $string, $length, $char ); + static function padright( $parser, $string = '', $length = 0, $padding = '0' ) { + return self::pad( $string, $length, $padding ); } static function anchorencode( $parser, $text ) { diff --git a/includes/parser/DateFormatter.php b/includes/parser/DateFormatter.php index 9ef11d5e..aa6415e4 100644 --- a/includes/parser/DateFormatter.php +++ b/includes/parser/DateFormatter.php @@ -41,11 +41,11 @@ class DateFormatter $this->regexTrail = '(?![a-z])/iu'; # Partial regular expressions - $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')]]'; - $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})]]'; - $this->prxY = '\[\[(\d{1,4}([ _]BC|))]]'; - $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})]]'; - $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})]]'; + $this->prxDM = '\[\[(\d{1,2})[ _](' . $this->monthNames . ')\]\]'; + $this->prxMD = '\[\[(' . $this->monthNames . ')[ _](\d{1,2})\]\]'; + $this->prxY = '\[\[(\d{1,4}([ _]BC|))\]\]'; + $this->prxISO1 = '\[\[(-?\d{4})]]-\[\[(\d{2})-(\d{2})\]\]'; + $this->prxISO2 = '\[\[(-?\d{4})-(\d{2})-(\d{2})\]\]'; # Real regular expressions $this->regexes[self::DMY] = "/{$this->prxDM} *,? *{$this->prxY}{$this->regexTrail}"; @@ -96,9 +96,11 @@ class DateFormatter } /** - * @static + * Get a DateFormatter object + * + * @return DateFormatter object */ - function &getInstance() { + public static function &getInstance() { global $wgMemc; static $dateFormatter = false; if ( !$dateFormatter ) { @@ -112,10 +114,14 @@ class DateFormatter } /** - * @param string $preference User preference - * @param string $text Text to reformat + * @param $preference String: User preference + * @param $text String: Text to reformat */ - function reformat( $preference, $text ) { + function reformat( $preference, $text, $options = array('linked') ) { + + $linked = in_array( 'linked', $options ); + $match_whole = in_array( 'match-whole', $options ); + if ( isset( $this->preferences[$preference] ) ) { $preference = $this->preferences[$preference]; } else { @@ -136,7 +142,24 @@ class DateFormatter # Default $this->mTarget = $i; } - $text = preg_replace_callback( $this->regexes[$i], array( &$this, 'replace' ), $text ); + $regex = $this->regexes[$i]; + + // Horrible hack + if (!$linked) { + $regex = str_replace( array( '\[\[', '\]\]' ), '', $regex ); + } + + if ($match_whole) { + // Let's hope this works + $regex = preg_replace( '!^/!', '/^', $regex ); + $regex = str_replace( $this->regexTrail, + '$'.$this->regexTrail, $regex ); + } + + // Another horrible hack + $this->mLinked = $linked; + $text = preg_replace_callback( $regex, array( &$this, 'replace' ), $text ); + unset($this->mLinked); } return $text; } @@ -146,6 +169,10 @@ class DateFormatter */ function replace( $matches ) { # Extract information from $matches + $linked = true; + if ( isset( $this->mLinked ) ) + $linked = $this->mLinked; + $bits = array(); $key = $this->keys[$this->mSource]; for ( $p=0; $p < strlen($key); $p++ ) { @@ -153,41 +180,54 @@ class DateFormatter $bits[$key{$p}] = $matches[$p+1]; } } - + + return $this->formatDate( $bits, $linked ); + } + + function formatDate( $bits, $link = true ) { $format = $this->targets[$this->mTarget]; + + if (!$link) { + // strip piped links + $format = preg_replace( '/\[\[[^|]+\|([^\]]+)\]\]/', '$1', $format ); + // strip remaining links + $format = str_replace( array( '[[', ']]' ), '', $format ); + } # Construct new date $text = ''; $fail = false; + + // Pre-generate y/Y stuff because we need the year for the <span> title. + if ( !isset( $bits['y'] ) && isset( $bits['Y'] ) ) + $bits['y'] = $this->makeIsoYear( $bits['Y'] ); + if ( !isset( $bits['Y'] ) && isset( $bits['y'] ) ) + $bits['Y'] = $this->makeNormalYear( $bits['y'] ); + + if ( !isset( $bits['m'] ) ) { + $m = $this->makeIsoMonth( $bits['F'] ); + if ( !$m || $m == '00' ) { + $fail = true; + } else { + $bits['m'] = $m; + } + } + + if ( !isset($bits['d']) ) { + $bits['d'] = sprintf( '%02d', $bits['j'] ); + } for ( $p=0; $p < strlen( $format ); $p++ ) { $char = $format{$p}; switch ( $char ) { case 'd': # ISO day of month - if ( !isset($bits['d']) ) { - $text .= sprintf( '%02d', $bits['j'] ); - } else { - $text .= $bits['d']; - } + $text .= $bits['d']; break; case 'm': # ISO month - if ( !isset($bits['m']) ) { - $m = $this->makeIsoMonth( $bits['F'] ); - if ( !$m || $m == '00' ) { - $fail = true; - } else { - $text .= $m; - } - } else { - $text .= $bits['m']; - } + $text .= $bits['m']; break; case 'y': # ISO year - if ( !isset( $bits['y'] ) ) { - $text .= $this->makeIsoYear( $bits['Y'] ); - } else { - $text .= $bits['y']; - } + $text .= $bits['y']; break; case 'j': # ordinary day of month if ( !isset($bits['j']) ) { @@ -210,11 +250,7 @@ class DateFormatter } break; case 'Y': # ordinary (optional BC) year - if ( !isset( $bits['Y'] ) ) { - $text .= $this->makeNormalYear( $bits['y'] ); - } else { - $text .= $bits['Y']; - } + $text .= $bits['Y']; break; default: $text .= $char; @@ -223,6 +259,18 @@ class DateFormatter if ( $fail ) { $text = $matches[0]; } + + $isoBits = array(); + if ( isset($bits['y']) ) + $isoBits[] = $bits['y']; + $isoBits[] = $bits['m']; + $isoBits[] = $bits['d']; + $isoDate = implode( '-', $isoBits );; + + // Output is not strictly HTML (it's wikitext), but <span> is whitelisted. + $text = Xml::tags( 'span', + array( 'class' => 'mw-formatted-date', 'title' => $isoDate ), $text ); + return $text; } diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 7fcfb90a..e6a68782 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -374,8 +374,8 @@ class Parser $text = Sanitizer::normalizeCharReferences( $text ); - if (($wgUseTidy and $this->mOptions->mTidy) or $wgAlwaysUseTidy) { - $text = self::tidy($text); + if ( ( $wgUseTidy && $this->mOptions->mTidy ) || $wgAlwaysUseTidy ) { + $text = MWTidy::tidy( $text ); } else { # attempt to sanitize at least some nesting problems # (bug #2702 and quite a few others) @@ -648,126 +648,14 @@ class Parser $this->mStripState->general->setPair( $rnd, $text ); return $rnd; } - - /** - * Interface with html tidy, used if $wgUseTidy = true. - * If tidy isn't able to correct the markup, the original will be - * returned in all its glory with a warning comment appended. - * - * Either the external tidy program or the in-process tidy extension - * will be used depending on availability. Override the default - * $wgTidyInternal setting to disable the internal if it's not working. - * - * @param string $text Hideous HTML input - * @return string Corrected HTML output - * @public - * @static - */ - function tidy( $text ) { - global $wgTidyInternal; - - $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'. -' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'. -'<head><title>test</title></head><body>'.$text.'</body></html>'; - - # Tidy is known to clobber tabs; convert 'em to entities - $wrappedtext = str_replace("\t", '	', $wrappedtext); - - if( $wgTidyInternal ) { - $correctedtext = self::internalTidy( $wrappedtext ); - } else { - $correctedtext = self::externalTidy( $wrappedtext ); - } - if( is_null( $correctedtext ) ) { - wfDebug( "Tidy error detected!\n" ); - return $text . "\n<!-- Tidy found serious XHTML errors -->\n"; - } - - # Convert the tabs back from entities - $correctedtext = str_replace('	', "\t", $correctedtext); - - return $correctedtext; - } - - /** - * Spawn an external HTML tidy process and get corrected markup back from it. - * - * @private - * @static - */ - function externalTidy( $text ) { - global $wgTidyConf, $wgTidyBin, $wgTidyOpts; - wfProfileIn( __METHOD__ ); - - $cleansource = ''; - $opts = ' -utf8'; - - $descriptorspec = array( - 0 => array('pipe', 'r'), - 1 => array('pipe', 'w'), - 2 => array('file', wfGetNull(), 'a') - ); - $pipes = array(); - if( function_exists('proc_open') ) { - $process = proc_open("$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes); - if (is_resource($process)) { - // Theoretically, this style of communication could cause a deadlock - // here. If the stdout buffer fills up, then writes to stdin could - // block. This doesn't appear to happen with tidy, because tidy only - // writes to stdout after it's finished reading from stdin. Search - // for tidyParseStdin and tidySaveStdout in console/tidy.c - fwrite($pipes[0], $text); - fclose($pipes[0]); - while (!feof($pipes[1])) { - $cleansource .= fgets($pipes[1], 1024); - } - fclose($pipes[1]); - proc_close($process); - } - } - - wfProfileOut( __METHOD__ ); - - if( $cleansource == '' && $text != '') { - // Some kind of error happened, so we couldn't get the corrected text. - // Just give up; we'll use the source text and append a warning. - return null; - } else { - return $cleansource; - } - } - + /** - * Use the HTML tidy PECL extension to use the tidy library in-process, - * saving the overhead of spawning a new process. - * - * 'pear install tidy' should be able to compile the extension module. - * - * @private - * @static + * Interface with html tidy + * @deprecated Use MWTidy::tidy() */ - function internalTidy( $text ) { - global $wgTidyConf, $IP, $wgDebugTidy; - wfProfileIn( __METHOD__ ); - - $tidy = new tidy; - $tidy->parseString( $text, $wgTidyConf, 'utf8' ); - $tidy->cleanRepair(); - if( $tidy->getStatus() == 2 ) { - // 2 is magic number for fatal error - // http://www.php.net/manual/en/function.tidy-get-status.php - $cleansource = null; - } else { - $cleansource = tidy_get_output( $tidy ); - } - if ( $wgDebugTidy && $tidy->getStatus() > 0 ) { - $cleansource .= "<!--\nTidy reports:\n" . - str_replace( '-->', '-->', $tidy->errorBuffer ) . - "\n-->"; - } - - wfProfileOut( __METHOD__ ); - return $cleansource; + public static function tidy( $text ) { + wfDeprecated( __METHOD__ ); + return MWTidy::tidy( $text ); } /** @@ -998,7 +886,7 @@ class Parser $text = $this->doDoubleUnderscore( $text ); $text = $this->doHeadings( $text ); - if($this->mOptions->getUseDynamicDates()) { + if( $this->mOptions->getUseDynamicDates() ) { $df = DateFormatter::getInstance(); $text = $df->reformat( $this->mOptions->getDateFormat(), $text ); } @@ -1008,7 +896,7 @@ class Parser # replaceInternalLinks may sometimes leave behind # absolute URLs, which have to be masked to hide them from replaceExternalLinks - $text = str_replace($this->mUniqPrefix."NOPARSE", "", $text); + $text = str_replace($this->mUniqPrefix.'NOPARSE', '', $text); $text = $this->doMagicLinks( $text ); $text = $this->formatHeadings( $text, $isMain ); @@ -1045,16 +933,16 @@ class Parser } function magicLinkCallback( $m ) { - if ( isset( $m[1] ) && strval( $m[1] ) !== '' ) { + if ( isset( $m[1] ) && $m[1] !== '' ) { # Skip anchor return $m[0]; - } elseif ( isset( $m[2] ) && strval( $m[2] ) !== '' ) { + } elseif ( isset( $m[2] ) && $m[2] !== '' ) { # Skip HTML element return $m[0]; - } elseif ( isset( $m[3] ) && strval( $m[3] ) !== '' ) { + } elseif ( isset( $m[3] ) && $m[3] !== '' ) { # Free external link return $this->makeFreeExternalLink( $m[0] ); - } elseif ( isset( $m[4] ) && strval( $m[4] ) !== '' ) { + } elseif ( isset( $m[4] ) && $m[4] !== '' ) { # RFC or PMID if ( substr( $m[0], 0, 3 ) === 'RFC' ) { $keyword = 'RFC'; @@ -1072,7 +960,7 @@ class Parser $sk = $this->mOptions->getSkin(); $la = $sk->getExternalLinkAttributes( $url, $keyword.$id ); return "<a href=\"{$url}\"{$la}>{$keyword} {$id}</a>"; - } elseif ( isset( $m[5] ) && strval( $m[5] ) !== '' ) { + } elseif ( isset( $m[5] ) && $m[5] !== '' ) { # ISBN $isbn = $m[5]; $num = strtr( $isbn, array( @@ -1130,7 +1018,7 @@ class Parser if ( $text === false ) { # Not an image, make a link $text = $sk->makeExternalLink( $url, $wgContLang->markNoConversion($url), true, 'free', - $this->getExternalLinkAttribs() ); + $this->getExternalLinkAttribs( $url ) ); # Register it in the output object... # Replace unnecessary URL escape codes with their equivalent characters $pasteurized = self::replaceUnusualEscapes( $url ); @@ -1406,18 +1294,12 @@ class Parser $url = Sanitizer::cleanUrl( $url ); - if ( $this->mOptions->mExternalLinkTarget ) { - $attribs = array( 'target' => $this->mOptions->mExternalLinkTarget ); - } else { - $attribs = array(); - } - # Use the encoded URL # This means that users can paste URLs directly into the text # Funny characters like ö aren't valid in URLs anyway # This was changed in August 2004 - $s .= $sk->makeExternalLink( $url, $text, false, $linktype, $this->getExternalLinkAttribs() ) - . $dtrail . $trail; + $s .= $sk->makeExternalLink( $url, $text, false, $linktype, + $this->getExternalLinkAttribs( $url ) ) . $dtrail . $trail; # Register link in the output object. # Replace unnecessary URL escape codes with the referenced character @@ -1430,12 +1312,36 @@ class Parser return $s; } - function getExternalLinkAttribs() { + /** + * Get an associative array of additional HTML attributes appropriate for a + * particular external link. This currently may include rel => nofollow + * (depending on configuration, namespace, and the URL's domain) and/or a + * target attribute (depending on configuration). + * + * @param string $url Optional URL, to extract the domain from for rel => + * nofollow if appropriate + * @return array Associative array of HTML attributes + */ + function getExternalLinkAttribs( $url = false ) { $attribs = array(); global $wgNoFollowLinks, $wgNoFollowNsExceptions; $ns = $this->mTitle->getNamespace(); if( $wgNoFollowLinks && !in_array($ns, $wgNoFollowNsExceptions) ) { $attribs['rel'] = 'nofollow'; + + global $wgNoFollowDomainExceptions; + if ( $wgNoFollowDomainExceptions ) { + $bits = wfParseUrl( $url ); + if ( is_array( $bits ) && isset( $bits['host'] ) ) { + foreach ( $wgNoFollowDomainExceptions as $domain ) { + if( substr( $bits['host'], -strlen( $domain ) ) + == $domain ) { + unset( $attribs['rel'] ); + break; + } + } + } + } } if ( $this->mOptions->getExternalLinkTarget() ) { $attribs['target'] = $this->mOptions->getExternalLinkTarget(); @@ -1697,7 +1603,7 @@ class Parser wfProfileOut( __METHOD__."-misc" ); wfProfileIn( __METHOD__."-title" ); $nt = Title::newFromText( $this->mStripState->unstripNoWiki($link) ); - if( !$nt ) { + if( $nt === NULL ) { $s .= $prefix . '[[' . $line; wfProfileOut( __METHOD__."-title" ); continue; @@ -1823,6 +1729,7 @@ class Parser # NS_MEDIA is a pseudo-namespace for linking directly to a file # FIXME: Should do batch file existence checks, see comment below if( $ns == NS_MEDIA ) { + wfProfileIn( __METHOD__."-media" ); # Give extensions a chance to select the file revision for us $skip = $time = false; wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$nt, &$skip, &$time ) ); @@ -1834,9 +1741,11 @@ class Parser # Cloak with NOPARSE to avoid replacement in replaceExternalLinks $s .= $prefix . $this->armorLinks( $link ) . $trail; $this->mOutput->addImage( $nt->getDBkey() ); + wfProfileOut( __METHOD__."-media" ); continue; } + wfProfileIn( __METHOD__."-always_known" ); # Some titles, such as valid special pages or files in foreign repos, should # be shown as bluelinks even though they're not included in the page table # @@ -1849,6 +1758,7 @@ class Parser # Links will be added to the output link list after checking $s .= $holders->makeHolder( $nt, $text, '', $trail, $prefix ); } + wfProfileOut( __METHOD__."-always_known" ); } wfProfileOut( __METHOD__ ); return $holders; @@ -2178,7 +2088,7 @@ class Parser $inBlockElem = true; } } else if ( !$inBlockElem && !$this->mInPre ) { - if ( ' ' == $t{0} and ( $this->mLastSection === 'pre' or trim($t) != '' ) ) { + if ( ' ' == substr( $t, 0, 1 ) and ( $this->mLastSection === 'pre' or trim($t) != '' ) ) { // pre if ($this->mLastSection !== 'pre') { $paragraphStack = false; @@ -2540,6 +2450,12 @@ class Parser $this->mOutput->setFlag( 'vary-revision' ); wfDebug( __METHOD__ . ": {{REVISIONTIMESTAMP}} used, setting vary-revision...\n" ); return $this->getRevisionTimestamp(); + case 'revisionuser': + // Let the edit saving system know we should parse the page + // *after* a revision ID has been assigned. This is for null edits. + $this->mOutput->setFlag( 'vary-revision' ); + wfDebug( __METHOD__ . ": {{REVISIONUSER}} used, setting vary-revision...\n" ); + return $this->getRevisionUser(); case 'namespace': return str_replace('_',' ',$wgContLang->getNsText( $this->mTitle->getNamespace() ) ); case 'namespacee': @@ -2586,6 +2502,8 @@ class Parser return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::images() ); case 'numberofusers': return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::users() ); + case 'numberofactiveusers': + return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::activeUsers() ); case 'numberofpages': return $this->mVarCache[$index] = $wgContLang->formatNum( SiteStats::pages() ); case 'numberofadmins': @@ -2696,11 +2614,10 @@ class Parser * @private */ function replaceVariables( $text, $frame = false, $argsOnly = false ) { - # Prevent too big inclusions - if( strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) { + # Is there any text? Also, Prevent too big inclusions! + if ( strlen( $text ) < 1 || strlen( $text ) > $this->mOptions->getMaxIncludeSize() ) { return $text; } - wfProfileIn( __METHOD__ ); if ( $frame === false ) { @@ -2776,7 +2693,7 @@ class Parser * @private */ function braceSubstitution( $piece, $frame ) { - global $wgContLang, $wgAllowDisplayTitle, $wgNonincludableNamespaces; + global $wgContLang, $wgNonincludableNamespaces; wfProfileIn( __METHOD__ ); wfProfileIn( __METHOD__.'-setup' ); @@ -2936,12 +2853,6 @@ class Parser if($wgContLang->hasVariants() && $title->getArticleID() == 0){ $wgContLang->findVariantLink( $part1, $title, true ); } - # Do infinite loop check - if ( !$frame->loopCheck( $title ) ) { - $found = true; - $text = '<span class="error">' . wfMsgForContent( 'parser-template-loop-warning', $titleText ) . '</span>'; - wfDebug( __METHOD__.": template loop broken at '$titleText'\n" ); - } # Do recursion depth check $limit = $this->mOptions->getMaxTemplateDepth(); if ( $frame->depth >= $limit ) { @@ -2991,6 +2902,14 @@ class Parser } $found = true; } + + # Do infinite loop check + # This has to be done after redirect resolution to avoid infinite loops via redirects + if ( !$frame->loopCheck( $title ) ) { + $found = true; + $text = '<span class="error">' . wfMsgForContent( 'parser-template-loop-warning', $titleText ) . '</span>'; + wfDebug( __METHOD__.": template loop broken at '$titleText'\n" ); + } wfProfileOut( __METHOD__ . '-loadtpl' ); } @@ -3304,6 +3223,7 @@ class Parser throw new MWException( '<html> extension tag encountered unexpectedly' ); } case 'nowiki': + $content = strtr($content, array('-{' => '-{', '}-' => '}-')); $output = Xml::escapeTagsOnly( $content ); break; case 'math': @@ -3387,6 +3307,7 @@ class Parser * Fills $this->mDoubleUnderscores, returns the modified text */ function doDoubleUnderscore( $text ) { + wfProfileIn( __METHOD__ ); // The position of __TOC__ needs to be recorded $mw = MagicWord::get( 'toc' ); if( $mw->match( $text ) ) { @@ -3429,7 +3350,7 @@ class Parser } elseif( isset( $this->mDoubleUnderscores['index'] ) ) { $this->mOutput->setIndexPolicy( 'index' ); } - + wfProfileOut( __METHOD__ ); return $text; } @@ -3459,7 +3380,7 @@ class Parser } # Inhibit editsection links if requested in the page - if ( isset( $this->mDoubleUnderscores['noeditsection'] ) ) { + if ( isset( $this->mDoubleUnderscores['noeditsection'] ) || $this->mOptions->getIsPrintable() ) { $showEditLink = 0; } @@ -3479,6 +3400,12 @@ class Parser $this->mOutput->setNewSection( true ); } + # Allow user to remove the "new section" + # link via __NONEWSECTIONLINK__ + if ( isset( $this->mDoubleUnderscores['nonewsectionlink'] ) ) { + $this->mOutput->hideNewSection( true ); + } + # if the string __FORCETOC__ (not case-sensitive) occurs in the HTML, # override above conditions and always show TOC above first header if ( isset( $this->mDoubleUnderscores['forcetoc'] ) ) { @@ -3762,13 +3689,13 @@ class Parser * * @param string $text the text to transform * @param Title &$title the Title object for the current article - * @param User &$user the User object describing the current user + * @param User $user the User object describing the current user * @param ParserOptions $options parsing options * @param bool $clearState whether to clear the parser state first * @return string the altered wiki markup * @public */ - function preSaveTransform( $text, &$title, $user, $options, $clearState = true ) { + function preSaveTransform( $text, Title $title, $user, $options, $clearState = true ) { $this->mOptions = $options; $this->setTitle( $title ); $this->setOutputType( self::OT_WIKI ); @@ -3808,6 +3735,15 @@ class Parser putenv( 'TZ='.$wgLocaltimezone ); $ts = date( 'YmdHis', $unixts ); $tz = date( 'T', $unixts ); # might vary on DST changeover! + + /* Allow translation of timezones trough wiki. date() can return + * whatever crap the system uses, localised or not, so we cannot + * ship premade translations. + */ + $key = 'timezone-' . strtolower( trim( $tz ) ); + $value = wfMsgForContent( $key ); + if ( !wfEmptyMsg( $key, $value ) ) $tz = $value; + putenv( 'TZ='.$oldtz ); } @@ -4627,7 +4563,11 @@ class Parser // Output the replacement text // Add two newlines on -- trailing whitespace in $newText is conventionally // stripped by the editor, so we need both newlines to restore the paragraph gap - $outText .= $newText . "\n\n"; + // Only add trailing whitespace if there is newText + if($newText != "") { + $outText .= $newText . "\n\n"; + } + while ( $node ) { $outText .= $frame->expand( $node, PPFrame::RECOVER_ORIG ); $node = $node->getNextSibling(); @@ -4694,6 +4634,22 @@ class Parser } /** + * Get the name of the user that edited the last revision + */ + function getRevisionUser() { + // if this template is subst: the revision id will be blank, + // so just use the current user's name + if( $this->mRevisionId ) { + $revision = Revision::newFromId( $this->mRevisionId ); + $revuser = $revision->getUserText(); + } else { + global $wgUser; + $revuser = $wgUser->getName(); + } + return $revuser; + } + + /** * Mutator for $mDefaultSort * * @param $sort New value @@ -4844,6 +4800,102 @@ class Parser } return $out; } + + function serialiseHalfParsedText( $text ) { + $data = array(); + $data['text'] = $text; + + // First, find all strip markers, and store their + // data in an array. + $stripState = new StripState; + $pos = 0; + while( ( $start_pos = strpos( $text, $this->mUniqPrefix, $pos ) ) && ( $end_pos = strpos( $text, self::MARKER_SUFFIX, $pos ) ) ) { + $end_pos += strlen( self::MARKER_SUFFIX ); + $marker = substr( $text, $start_pos, $end_pos-$start_pos ); + + if ( !empty( $this->mStripState->general->data[$marker] ) ) { + $replaceArray = $stripState->general; + $stripText = $this->mStripState->general->data[$marker]; + } elseif ( !empty( $this->mStripState->nowiki->data[$marker] ) ) { + $replaceArray = $stripState->nowiki; + $stripText = $this->mStripState->nowiki->data[$marker]; + } else { + throw new MWException( "Hanging strip marker: '$marker'." ); + } + + $replaceArray->setPair( $marker, $stripText ); + $pos = $end_pos; + } + $data['stripstate'] = $stripState; + + // Now, find all of our links, and store THEIR + // data in an array! :) + $links = array( 'internal' => array(), 'interwiki' => array() ); + $pos = 0; + + // Internal links + while( ( $start_pos = strpos( $text, '<!--LINK ', $pos ) ) ) { + list( $ns, $trail ) = explode( ':', substr( $text, $start_pos + strlen( '<!--LINK ' ) ), 2 ); + + $ns = trim($ns); + if (empty( $links['internal'][$ns] )) { + $links['internal'][$ns] = array(); + } + + $key = trim( substr( $trail, 0, strpos( $trail, '-->' ) ) ); + $links['internal'][$ns][] = $this->mLinkHolders->internals[$ns][$key]; + $pos = $start_pos + strlen( "<!--LINK $ns:$key-->" ); + } + + $pos = 0; + + // Interwiki links + while( ( $start_pos = strpos( $text, '<!--IWLINK ', $pos ) ) ) { + $data = substr( $text, $start_pos ); + $key = trim( substr( $data, 0, strpos( $data, '-->' ) ) ); + $links['interwiki'][] = $this->mLinkHolders->interwiki[$key]; + $pos = $start_pos + strlen( "<!--IWLINK $key-->" ); + } + + $data['linkholder'] = $links; + + return $data; + } + + function unserialiseHalfParsedText( $data, $intPrefix = null /* Unique identifying prefix */ ) { + if (!$intPrefix) + $intPrefix = $this->getRandomString(); + + // First, extract the strip state. + $stripState = $data['stripstate']; + $this->mStripState->general->merge( $stripState->general ); + $this->mStripState->nowiki->merge( $stripState->nowiki ); + + // Now, extract the text, and renumber links + $text = $data['text']; + $links = $data['linkholder']; + + // Internal... + foreach( $links['internal'] as $ns => $nsLinks ) { + foreach( $nsLinks as $key => $entry ) { + $newKey = $intPrefix . '-' . $key; + $this->mLinkHolders->internals[$ns][$newKey] = $entry; + + $text = str_replace( "<!--LINK $ns:$key-->", "<!--LINK $ns:$newKey-->", $text ); + } + } + + // Interwiki... + foreach( $links['interwiki'] as $key => $entry ) { + $newKey = "$intPrefix-$key"; + $this->mLinkHolders->interwikis[$newKey] = $entry; + + $text = str_replace( "<!--IWLINK $key-->", "<!--IWLINK $newKey-->", $text ); + } + + // Should be good to go. + return $text; + } } /** diff --git a/includes/parser/ParserCache.php b/includes/parser/ParserCache.php index 7e61157a..d17214c3 100644 --- a/includes/parser/ParserCache.php +++ b/includes/parser/ParserCache.php @@ -26,8 +26,14 @@ class ParserCache { $this->mMemc =& $memCached; } - function getKey( &$article, &$user ) { - global $action; + function getKey( &$article, $popts ) { + global $wgRequest; + + if( $popts instanceof User ) // It used to be getKey( &$article, &$user ) + $popts = ParserOptions::newFromUser( $popts ); + + $user = $popts->mUser; + $printable = ( $popts->getIsPrintable() ) ? '!printable=1' : ''; $hash = $user->getPageRenderingHash(); if( !$article->mTitle->quickUserCan( 'edit' ) ) { // section edit links are suppressed even if the user has them on @@ -36,21 +42,21 @@ class ParserCache { $edit = ''; } $pageid = $article->getID(); - $renderkey = (int)($action == 'render'); - $key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}{$edit}" ); + $renderkey = (int)($wgRequest->getVal('action') == 'render'); + $key = wfMemcKey( 'pcache', 'idhash', "{$pageid}-{$renderkey}!{$hash}{$edit}{$printable}" ); return $key; } - function getETag( &$article, &$user ) { - return 'W/"' . $this->getKey($article, $user) . "--" . $article->mTouched. '"'; + function getETag( &$article, $popts ) { + return 'W/"' . $this->getKey($article, $popts) . "--" . $article->mTouched. '"'; } - function get( &$article, &$user ) { + function get( &$article, $popts ) { global $wgCacheEpoch; $fname = 'ParserCache::get'; wfProfileIn( $fname ); - $key = $this->getKey( $article, $user ); + $key = $this->getKey( $article, $popts ); wfDebug( "Trying parser cache $key\n" ); $value = $this->mMemc->get( $key ); @@ -86,9 +92,9 @@ class ParserCache { return $value; } - function save( $parserOutput, &$article, &$user ){ + function save( $parserOutput, &$article, $popts ){ global $wgParserCacheExpireTime; - $key = $this->getKey( $article, $user ); + $key = $this->getKey( $article, $popts ); if( $parserOutput->getCacheTime() != -1 ) { diff --git a/includes/parser/ParserOptions.php b/includes/parser/ParserOptions.php index 5b8cd3ee..e6a9f3a7 100644 --- a/includes/parser/ParserOptions.php +++ b/includes/parser/ParserOptions.php @@ -33,7 +33,10 @@ class ParserOptions var $mExternalLinkTarget; # Target attribute for external links var $mUser; # Stored user object, just used to initialise the skin - + var $mIsPreview; # Parsing the page for a "preview" operation + var $mIsSectionPreview; # Parsing the page for a "preview" operation on a single section + var $mIsPrintable; # Parsing the printable version of the page + function getUseTeX() { return $this->mUseTeX; } function getUseDynamicDates() { return $this->mUseDynamicDates; } function getInterwikiMagic() { return $this->mInterwikiMagic; } @@ -54,7 +57,10 @@ class ParserOptions function getEnableLimitReport() { return $this->mEnableLimitReport; } function getCleanSignatures() { return $this->mCleanSignatures; } function getExternalLinkTarget() { return $this->mExternalLinkTarget; } - + function getIsPreview() { return $this->mIsPreview; } + function getIsSectionPreview() { return $this->mIsSectionPreview; } + function getIsPrintable() { return $this->mIsPrintable; } + function getSkin() { if ( !isset( $this->mSkin ) ) { $this->mSkin = $this->mUser->getSkin(); @@ -99,7 +105,10 @@ class ParserOptions function setTimestamp( $x ) { return wfSetVar( $this->mTimestamp, $x ); } function setCleanSignatures( $x ) { return wfSetVar( $this->mCleanSignatures, $x ); } function setExternalLinkTarget( $x ) { return wfSetVar( $this->mExternalLinkTarget, $x ); } - + function setIsPreview( $x ) { return wfSetVar( $this->mIsPreview, $x ); } + function setIsSectionPreview( $x ) { return wfSetVar( $this->mIsSectionPreview, $x ); } + function setIsPrintable( $x ) { return wfSetVar( $this->mIsPrintable, $x ); } + function __construct( $user = null ) { $this->initialiseFromUser( $user ); } @@ -156,6 +165,8 @@ class ParserOptions $this->mEnableLimitReport = false; $this->mCleanSignatures = $wgCleanSignatures; $this->mExternalLinkTarget = $wgExternalLinkTarget; + $this->mIsPreview = false; + $this->mIsSectionPreview = false; wfProfileOut( $fname ); } } diff --git a/includes/parser/ParserOutput.php b/includes/parser/ParserOutput.php index 35cb5c92..22c1dfba 100644 --- a/includes/parser/ParserOutput.php +++ b/includes/parser/ParserOutput.php @@ -18,6 +18,7 @@ class ParserOutput $mImages = array(), # DB keys of the images used, in the array key only $mExternalLinks = array(), # External link URLs, in the key only $mNewSection = false, # Show a new section link? + $mHideNewSection = false, # Hide the new section link? $mNoGallery = false, # No gallery on category page? (__NOGALLERY__) $mHeadItems = array(), # Items to put in the <head> section $mOutputHooks = array(), # Hook tags as per $wgParserOutputHooks @@ -80,6 +81,12 @@ class ParserOutput function setNewSection( $value ) { $this->mNewSection = (bool)$value; } + function hideNewSection ( $value ) { + $this->mHideNewSection = (bool)$value; + } + function getHideNewSection () { + return (bool)$this->mHideNewSection; + } function getNewSection() { return (bool)$this->mNewSection; } @@ -94,6 +101,9 @@ class ParserOutput // We don't record Special: links currently // It might actually be wise to, but we'd need to do some normalization. return; + } elseif( $dbk === '' ) { + // Don't record self links - [[#Foo]] + return; } if ( !isset( $this->mLinks[$ns] ) ) { $this->mLinks[$ns] = array(); diff --git a/includes/parser/Preprocessor_DOM.php b/includes/parser/Preprocessor_DOM.php index af591b67..2e114545 100644 --- a/includes/parser/Preprocessor_DOM.php +++ b/includes/parser/Preprocessor_DOM.php @@ -6,6 +6,8 @@ class Preprocessor_DOM implements Preprocessor { var $parser, $memoryLimit; + const CACHE_VERSION = 1; + function __construct( $parser ) { $this->parser = $parser; $mem = ini_get( 'memory_limit' ); @@ -63,8 +65,61 @@ class Preprocessor_DOM implements Preprocessor { */ function preprocessToObj( $text, $flags = 0 ) { wfProfileIn( __METHOD__ ); - wfProfileIn( __METHOD__.'-makexml' ); + global $wgMemc, $wgPreprocessorCacheThreshold; + + $xml = false; + $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold; + if ( $cacheable ) { + wfProfileIn( __METHOD__.'-cacheable' ); + + $cacheKey = wfMemcKey( 'preprocess-xml', md5($text), $flags ); + $cacheValue = $wgMemc->get( $cacheKey ); + if ( $cacheValue ) { + $version = substr( $cacheValue, 0, 8 ); + if ( intval( $version ) == self::CACHE_VERSION ) { + $xml = substr( $cacheValue, 8 ); + // From the cache + wfDebugLog( "Preprocessor", "Loaded preprocessor XML from memcached (key $cacheKey)" ); + } + } + } + if ( $xml === false ) { + if ( $cacheable ) { + wfProfileIn( __METHOD__.'-cache-miss' ); + $xml = $this->preprocessToXml( $text, $flags ); + $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . $xml; + $wgMemc->set( $cacheKey, $cacheValue, 86400 ); + wfProfileOut( __METHOD__.'-cache-miss' ); + wfDebugLog( "Preprocessor", "Saved preprocessor XML to memcached (key $cacheKey)" ); + } else { + $xml = $this->preprocessToXml( $text, $flags ); + } + } + wfProfileIn( __METHOD__.'-loadXML' ); + $dom = new DOMDocument; + wfSuppressWarnings(); + $result = $dom->loadXML( $xml ); + wfRestoreWarnings(); + if ( !$result ) { + // Try running the XML through UtfNormal to get rid of invalid characters + $xml = UtfNormal::cleanUp( $xml ); + $result = $dom->loadXML( $xml ); + if ( !$result ) { + throw new MWException( __METHOD__.' generated invalid XML' ); + } + } + $obj = new PPNode_DOM( $dom->documentElement ); + wfProfileOut( __METHOD__.'-loadXML' ); + if ( $cacheable ) { + wfProfileOut( __METHOD__.'-cacheable' ); + } + wfProfileOut( __METHOD__ ); + return $obj; + } + + function preprocessToXml( $text, $flags = 0 ) { + wfProfileIn( __METHOD__ ); $rules = array( '{' => array( 'end' => '}', @@ -304,7 +359,9 @@ class Preprocessor_DOM implements Preprocessor { } else { $attrEnd = $tagEndPos; // Find closing tag - if ( preg_match( "/<\/$name\s*>/i", $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) { + if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", + $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) + { $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); $i = $matches[0][1] + strlen( $matches[0][0] ); $close = '<close>' . htmlspecialchars( $matches[0][0] ) . '</close>'; @@ -569,24 +626,9 @@ class Preprocessor_DOM implements Preprocessor { $stack->rootAccum .= '</root>'; $xml = $stack->rootAccum; - wfProfileOut( __METHOD__.'-makexml' ); - wfProfileIn( __METHOD__.'-loadXML' ); - $dom = new DOMDocument; - wfSuppressWarnings(); - $result = $dom->loadXML( $xml ); - wfRestoreWarnings(); - if ( !$result ) { - // Try running the XML through UtfNormal to get rid of invalid characters - $xml = UtfNormal::cleanUp( $xml ); - $result = $dom->loadXML( $xml ); - if ( !$result ) { - throw new MWException( __METHOD__.' generated invalid XML' ); - } - } - $obj = new PPNode_DOM( $dom->documentElement ); - wfProfileOut( __METHOD__.'-loadXML' ); wfProfileOut( __METHOD__ ); - return $obj; + + return $xml; } } @@ -831,7 +873,6 @@ class PPFrame_DOM implements PPFrame { if ( is_string( $root ) ) { return $root; } - wfProfileIn( __METHOD__ ); if ( ++$this->parser->mPPNodeCount > $this->parser->mOptions->mMaxPPNodeCount ) { @@ -841,6 +882,7 @@ class PPFrame_DOM implements PPFrame { if ( $expansionDepth > $this->parser->mOptions->mMaxPPExpandDepth ) { return '<span class="error">Expansion depth limit exceeded</span>'; } + wfProfileIn( __METHOD__ ); ++$expansionDepth; if ( $root instanceof PPNode_DOM ) { diff --git a/includes/parser/Preprocessor_Hash.php b/includes/parser/Preprocessor_Hash.php index 62028291..f46ee40c 100644 --- a/includes/parser/Preprocessor_Hash.php +++ b/includes/parser/Preprocessor_Hash.php @@ -8,6 +8,8 @@ */ class Preprocessor_Hash implements Preprocessor { var $parser; + + const CACHE_VERSION = 1; function __construct( $parser ) { $this->parser = $parser; @@ -45,6 +47,31 @@ class Preprocessor_Hash implements Preprocessor { */ function preprocessToObj( $text, $flags = 0 ) { wfProfileIn( __METHOD__ ); + + + // Check cache. + global $wgMemc, $wgPreprocessorCacheThreshold; + + $cacheable = strlen( $text ) > $wgPreprocessorCacheThreshold; + if ( $cacheable ) { + wfProfileIn( __METHOD__.'-cacheable' ); + + $cacheKey = wfMemcKey( 'preprocess-hash', md5($text), $flags ); + $cacheValue = $wgMemc->get( $cacheKey ); + if ( $cacheValue ) { + $version = substr( $cacheValue, 0, 8 ); + if ( intval( $version ) == self::CACHE_VERSION ) { + $hash = unserialize( substr( $cacheValue, 8 ) ); + // From the cache + wfDebugLog( "Preprocessor", + "Loaded preprocessor hash from memcached (key $cacheKey)" ); + wfProfileOut( __METHOD__.'-cacheable' ); + wfProfileOut( __METHOD__ ); + return $hash; + } + } + wfProfileIn( __METHOD__.'-cache-miss' ); + } $rules = array( '{' => array( @@ -288,7 +315,9 @@ class Preprocessor_Hash implements Preprocessor { } else { $attrEnd = $tagEndPos; // Find closing tag - if ( preg_match( "/<\/$name\s*>/i", $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) { + if ( preg_match( "/<\/" . preg_quote( $name, '/' ) . "\s*>/i", + $text, $matches, PREG_OFFSET_CAPTURE, $tagEndPos + 1 ) ) + { $inner = substr( $text, $tagEndPos + 1, $matches[0][1] - $tagEndPos - 1 ); $i = $matches[0][1] + strlen( $matches[0][0] ); $close = $matches[0][0]; @@ -615,6 +644,16 @@ class Preprocessor_Hash implements Preprocessor { $rootNode = new PPNode_Hash_Tree( 'root' ); $rootNode->firstChild = $stack->rootAccum->firstNode; $rootNode->lastChild = $stack->rootAccum->lastNode; + + // Cache + if ($cacheable) { + $cacheValue = sprintf( "%08d", self::CACHE_VERSION ) . serialize( $rootNode );; + $wgMemc->set( $cacheKey, $cacheValue, 86400 ); + wfProfileOut( __METHOD__.'-cache-miss' ); + wfProfileOut( __METHOD__.'-cacheable' ); + wfDebugLog( "Preprocessor", "Saved preprocessor Hash to memcached (key $cacheKey)" ); + } + wfProfileOut( __METHOD__ ); return $rootNode; } diff --git a/includes/parser/Tidy.php b/includes/parser/Tidy.php new file mode 100644 index 00000000..95f83621 --- /dev/null +++ b/includes/parser/Tidy.php @@ -0,0 +1,170 @@ +<?php + +/** + * Class to interact with HTML tidy + * + * Either the external tidy program or the in-process tidy extension + * will be used depending on availability. Override the default + * $wgTidyInternal setting to disable the internal if it's not working. + * + * @ingroup Parser + */ +class MWTidy { + + /** + * Interface with html tidy, used if $wgUseTidy = true. + * If tidy isn't able to correct the markup, the original will be + * returned in all its glory with a warning comment appended. + * + * @param string $text Hideous HTML input + * @return string Corrected HTML output + */ + public static function tidy( $text ) { + global $wgTidyInternal; + + $wrappedtext = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"'. +' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html>'. +'<head><title>test</title></head><body>'.$text.'</body></html>'; + + # Tidy is known to clobber tabs; convert them to entities + $wrappedtext = str_replace( "\t", '	', $wrappedtext ); + + if( $wgTidyInternal ) { + $correctedtext = self::execInternalTidy( $wrappedtext ); + } else { + $correctedtext = self::execExternalTidy( $wrappedtext ); + } + if( is_null( $correctedtext ) ) { + wfDebug( "Tidy error detected!\n" ); + return $text . "\n<!-- Tidy found serious XHTML errors -->\n"; + } + + # Convert the tabs back from entities + $correctedtext = str_replace( '	', "\t", $correctedtext ); + + return $correctedtext; + } + + /** + * Check HTML for errors, used if $wgValidateAllHtml = true. + * + * @param $text String + * @param &$errorStr String: return the error string + * @return Boolean: whether the HTML is valid + */ + public static function checkErrors( $text, &$errorStr = null ) { + global $wgTidyInternal; + + $retval = 0; + if( $wgTidyInternal ) { + $errorStr = self::execInternalTidy( $text, true, $retval ); + } else { + $errorStr = self::execExternalTidy( $text, true, $retval ); + } + return ( $retval < 0 && $errorStr == '' ) || $retval == 0; + } + + /** + * Spawn an external HTML tidy process and get corrected markup back from it. + * Also called in OutputHandler.php for full page validation + * + * @param $text String: HTML to check + * @param $stderr Boolean: Whether to read from STDERR rather than STDOUT + * @param &$retval Exit code (-1 on internal error) + * @retrun mixed String or null + */ + private static function execExternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $wgTidyBin, $wgTidyOpts; + wfProfileIn( __METHOD__ ); + + $cleansource = ''; + $opts = ' -utf8'; + + if( $stderr ) { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'file', wfGetNull(), 'a' ), + 2 => array( 'pipe', 'w' ) + ); + } else { + $descriptorspec = array( + 0 => array( 'pipe', 'r' ), + 1 => array( 'pipe', 'w' ), + 2 => array( 'file', wfGetNull(), 'a' ) + ); + } + + $readpipe = $stderr ? 2 : 1; + $pipes = array(); + + if( function_exists( 'proc_open' ) ) { + $process = proc_open( "$wgTidyBin -config $wgTidyConf $wgTidyOpts$opts", $descriptorspec, $pipes ); + if ( is_resource( $process ) ) { + // Theoretically, this style of communication could cause a deadlock + // here. If the stdout buffer fills up, then writes to stdin could + // block. This doesn't appear to happen with tidy, because tidy only + // writes to stdout after it's finished reading from stdin. Search + // for tidyParseStdin and tidySaveStdout in console/tidy.c + fwrite( $pipes[0], $text ); + fclose( $pipes[0] ); + while ( !feof( $pipes[$readpipe] ) ) { + $cleansource .= fgets( $pipes[$readpipe], 1024 ); + } + fclose( $pipes[$readpipe] ); + $retval = proc_close( $process ); + } else { + $retval = -1; + } + } else { + $retval = -1; + } + + wfProfileOut( __METHOD__ ); + + if( !$stderr && $cleansource == '' && $text != '' ) { + // Some kind of error happened, so we couldn't get the corrected text. + // Just give up; we'll use the source text and append a warning. + return null; + } else { + return $cleansource; + } + } + + /** + * Use the HTML tidy PECL extension to use the tidy library in-process, + * saving the overhead of spawning a new process. + * + * 'pear install tidy' should be able to compile the extension module. + */ + private static function execInternalTidy( $text, $stderr = false, &$retval = null ) { + global $wgTidyConf, $IP, $wgDebugTidy; + wfProfileIn( __METHOD__ ); + + $tidy = new tidy; + $tidy->parseString( $text, $wgTidyConf, 'utf8' ); + + if( $stderr ) { + $retval = $tidy->getStatus(); + return $tidy->errorBuffer; + } else { + $tidy->cleanRepair(); + $retval = $tidy->getStatus(); + if( $retval == 2 ) { + // 2 is magic number for fatal error + // http://www.php.net/manual/en/function.tidy-get-status.php + $cleansource = null; + } else { + $cleansource = tidy_get_output( $tidy ); + } + if ( $wgDebugTidy && $retval > 0 ) { + $cleansource .= "<!--\nTidy reports:\n" . + str_replace( '-->', '-->', $tidy->errorBuffer ) . + "\n-->"; + } + + wfProfileOut( __METHOD__ ); + return $cleansource; + } + } + +} diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php index 0ff94b49..38181c08 100644 --- a/includes/specials/SpecialAllmessages.php +++ b/includes/specials/SpecialAllmessages.php @@ -10,7 +10,7 @@ */ function wfSpecialAllmessages() { global $wgOut, $wgRequest, $wgMessageCache, $wgTitle; - global $wgUseDatabaseMessages; + global $wgUseDatabaseMessages, $wgLang; # The page isn't much use if the MediaWiki namespace is not being used if( !$wgUseDatabaseMessages ) { @@ -49,16 +49,22 @@ function wfSpecialAllmessages() { $wgOut->addScriptFile( 'allmessages.js' ); if ( $ot == 'php' ) { $navText .= wfAllMessagesMakePhp( $messages ); - $wgOut->addHTML( 'PHP | <a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a> | ' . + $wgOut->addHTML( $wgLang->pipeList( array( + 'PHP', + '<a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a>', '<a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' . - '<pre>' . htmlspecialchars( $navText ) . '</pre>' ); + '<pre>' . htmlspecialchars( $navText ) . '</pre>' + ) ) ); } else if ( $ot == 'xml' ) { $wgOut->disable(); header( 'Content-type: text/xml' ); echo wfAllMessagesMakeXml( $messages ); } else { - $wgOut->addHTML( '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a> | ' . - 'HTML | <a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' ); + $wgOut->addHTML( $wgLang->pipeList( array( + '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a>', + 'HTML', + '<a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' + ) ) ); $wgOut->addWikiText( $navText ); $wgOut->addHTML( wfAllMessagesMakeHTMLText( $messages ) ); } diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php index bf68dfa6..bded8835 100644 --- a/includes/specials/SpecialAllpages.php +++ b/includes/specials/SpecialAllpages.php @@ -27,7 +27,7 @@ class SpecialAllpages extends IncludableSpecialPage { protected $nsfromMsg = 'allpagesfrom'; function __construct( $name = 'Allpages' ){ - parent::__construct( $name ); + parent::__construct( $name ); } /** @@ -217,9 +217,9 @@ class SpecialAllpages extends IncludableSpecialPage { $out2 .= '<tr valign="top"><td>' . $nsForm; $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . $wgUser->getSkin()->makeKnownLinkObj( $this->getTitle(), wfMsgHtml ( 'allpages' ) ); - $out2 .= "</td></tr></table><hr />"; + $out2 .= "</td></tr></table>"; } else { - $out2 = $nsForm . '<hr />'; + $out2 = $nsForm; } } $wgOut->addHTML( $out2 . $out ); @@ -237,8 +237,8 @@ class SpecialAllpages extends IncludableSpecialPage { $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) ); $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) ); // Don't let the length runaway - $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength, '...' ); - $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength, '...' ); + $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength ); + $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength ); $queryparams = $namespace ? "namespace=$namespace&" : ''; $special = $this->getTitle(); @@ -257,7 +257,7 @@ class SpecialAllpages extends IncludableSpecialPage { * @param string $to list all pages to this name (default FALSE) */ function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) { - global $wgOut, $wgUser, $wgContLang; + global $wgOut, $wgUser, $wgContLang, $wgLang; $sk = $wgUser->getSkin(); @@ -382,7 +382,7 @@ class SpecialAllpages extends IncludableSpecialPage { . ( $namespace ? '&namespace=' . $namespace : '' ); $prevLink = $sk->makeKnownLinkObj( $self, wfMsgHTML( 'prevpage', htmlspecialchars( $pt ) ), $q ); - $out2 .= ' | ' . $prevLink; + $out2 = $wgLang->pipeList( array( $out2, $prevLink ) ); } if( $n == $this->maxPerPage && $s = $res->fetchObject() ) { @@ -392,9 +392,9 @@ class SpecialAllpages extends IncludableSpecialPage { . ( $namespace ? '&namespace=' . $namespace : '' ); $nextLink = $sk->makeKnownLinkObj( $self, wfMsgHtml( 'nextpage', htmlspecialchars( $t->getText() ) ), $q ); - $out2 .= ' | ' . $nextLink; + $out2 = $wgLang->pipeList( array( $out2, $nextLink ) ); } - $out2 .= "</td></tr></table><hr />"; + $out2 .= "</td></tr></table>"; } $wgOut->addHTML( $out2 . $out ); @@ -404,7 +404,7 @@ class SpecialAllpages extends IncludableSpecialPage { $wgOut->addHTML( $prevLink ); } if( isset( $prevLink ) && isset( $nextLink ) ) { - $wgOut->addHTML( ' | ' ); + $wgOut->addHTML( wfMsgExt( 'pipe-separator' , 'escapenoentities' ) ); } if( isset( $nextLink ) ) { $wgOut->addHTML( $nextLink ); diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php index fdabe49d..29d6b96c 100644 --- a/includes/specials/SpecialBlankpage.php +++ b/includes/specials/SpecialBlankpage.php @@ -2,5 +2,5 @@ function wfSpecialBlankpage() { global $wgOut; - $wgOut->addHTML(wfMsg('intentionallyblankpage')); + $wgOut->addWikiMsg('intentionallyblankpage'); } diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php index 4d82997f..0efaedf1 100644 --- a/includes/specials/SpecialBlockip.php +++ b/includes/specials/SpecialBlockip.php @@ -45,6 +45,8 @@ function wfSpecialBlockip( $par ) { class IPBlockForm { var $BlockAddress, $BlockExpiry, $BlockReason; # var $BlockEmail; + // The maximum number of edits a user can have and still be hidden + const HIDEUSER_CONTRIBLIMIT = 1000; function IPBlockForm( $par ) { global $wgRequest, $wgUser, $wgBlockAllowsUTEdit; @@ -106,6 +108,19 @@ class IPBlockForm { ( $currentBlock->mAddress == $this->BlockAddress ) ) ) { $wgOut->addWikiMsg( 'ipb-needreblock', $this->BlockAddress ); $alreadyBlocked = true; + # Set the block form settings to the existing block + $this->BlockAnonOnly = $currentBlock->mAnonOnly; + $this->BlockCreateAccount = $currentBlock->mCreateAccount; + $this->BlockEnableAutoblock = $currentBlock->mEnableAutoblock; + $this->BlockEmail = $currentBlock->mBlockEmail; + $this->BlockHideName = $currentBlock->mHideName; + $this->BlockAllowUsertalk = $currentBlock->mAllowUsertalk; + if( $currentBlock->mExpiry == 'infinity' ) { + $this->BlockOther = 'indefinite'; + } else { + $this->BlockOther = wfTimestamp( TS_ISO_8601, $currentBlock->mExpiry ); + } + $this->BlockReason = $currentBlock->mReason; } } @@ -238,11 +253,11 @@ class IPBlockForm { $wgOut->addHTML(" <tr id='wpEnableHideUser'> <td> </td> - <td class='mw-input'>" . + <td class='mw-input'><strong>" . Xml::checkLabel( wfMsg( 'ipbhidename' ), 'wpHideName', 'wpHideName', $this->BlockHideName, array( 'tabindex' => '10' ) ) . " - </td> + </strong></td> </tr>" ); } @@ -363,7 +378,7 @@ class IPBlockForm { $reasonstr = $this->BlockReasonList; if ( $reasonstr != 'other' && $this->BlockReason != '' ) { // Entry from drop down menu + additional comment - $reasonstr .= ': ' . $this->BlockReason; + $reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->BlockReason; } elseif ( $reasonstr == 'other' ) { $reasonstr = $this->BlockReason; } @@ -381,9 +396,18 @@ class IPBlockForm { return array('ipb_expiry_invalid'); } - if( $this->BlockHideName && $expiry != 'infinity' ) { - // Bad expiry. - return array('ipb_expiry_temp'); + if( $this->BlockHideName ) { + if( !$userId ) { + // IP users should not be hidden + $this->BlockHideName = false; + } else if( $expiry !== 'infinity' ) { + // Bad expiry. + return array('ipb_expiry_temp'); + } else if( User::edits($userId) > self::HIDEUSER_CONTRIBLIMIT ) { + // Typically, the user should have a handful of edits. + // Disallow hiding users with many edits for performance. + return array('ipb_hide_invalid'); + } } # Create block @@ -391,13 +415,18 @@ class IPBlockForm { $block = new Block( $this->BlockAddress, $userId, $wgUser->getId(), $reasonstr, wfTimestampNow(), 0, $expiry, $this->BlockAnonOnly, $this->BlockCreateAccount, $this->BlockEnableAutoblock, $this->BlockHideName, - $this->BlockEmail, isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit ); + $this->BlockEmail, isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit + ); + # Should this be privately logged? + $suppressLog = (bool)$this->BlockHideName; if ( wfRunHooks('BlockIp', array(&$block, &$wgUser)) ) { - + # Try to insert block. Is there a conflicting block? if ( !$block->insert() ) { + # Show form unless the user is already aware of this... if ( !$this->BlockReblock ) { return array( 'ipb_already_blocked' ); + # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes @@ -405,18 +434,40 @@ class IPBlockForm { if( $block->equals( $currentBlock ) ) { return array( 'ipb_already_blocked' ); } + # If the name was hidden and the blocking user cannot hide + # names, then don't allow any block changes... + if( $currentBlock->mHideName && !$wgUser->isAllowed('hideuser') ) { + return array( 'hookaborted' ); + } $currentBlock->delete(); $block->insert(); + # If hiding/unhiding a name, this should go in the private logs + $suppressLog = $suppressLog || (bool)$currentBlock->mHideName; $log_action = 'reblock'; + # Unset _deleted fields if requested + if( $currentBlock->mHideName && !$this->BlockHideName ) { + self::unsuppressUserName( $this->BlockAddress, $userId ); + } } } else { $log_action = 'block'; } wfRunHooks('BlockIpComplete', array($block, $wgUser)); - if ( $this->BlockWatchUser ) { + # Set *_deleted fields if requested + if( $this->BlockHideName ) { + self::suppressUserName( $this->BlockAddress, $userId ); + } + + if ( $this->BlockWatchUser && + # Only show watch link when this is no range block + $block->mRangeStart == $block->mRangeEnd) { $wgUser->addWatch ( Title::makeTitle( NS_USER, $this->BlockAddress ) ); } + + # Block constructor sanitizes certain block options on insert + $this->BlockEmail = $block->mBlockEmail; + $this->BlockEnableAutoblock = $block->mEnableAutoblock; # Prepare log parameters $logParams = array(); @@ -424,16 +475,70 @@ class IPBlockForm { $logParams[] = $this->blockLogFlags(); # Make log entry, if the name is hidden, put it in the oversight log - $log_type = ($this->BlockHideName) ? 'suppress' : 'block'; + $log_type = $suppressLog ? 'suppress' : 'block'; $log = new LogPage( $log_type ); $log->addEntry( $log_action, Title::makeTitle( NS_USER, $this->BlockAddress ), $reasonstr, $logParams ); # Report to the user return array(); - } - else + } else { return array('hookaborted'); + } + } + + public static function suppressUserName( $name, $userId ) { + $op = '|'; // bitwise OR + return self::setUsernameBitfields( $name, $userId, $op ); + } + + public static function unsuppressUserName( $name, $userId ) { + $op = '&'; // bitwise AND + return self::setUsernameBitfields( $name, $userId, $op ); + } + + private static function setUsernameBitfields( $name, $userId, $op ) { + if( $op !== '|' && $op !== '&' ) + return false; // sanity check + $dbw = wfGetDB( DB_MASTER ); + $delUser = Revision::DELETED_USER | Revision::DELETED_RESTRICTED; + $delAction = LogPage::DELETED_ACTION | Revision::DELETED_RESTRICTED; + # Normalize user name + $userTitle = Title::makeTitleSafe( NS_USER, $name ); + $userDbKey = $userTitle->getDBKey(); + # To suppress, we OR the current bitfields with Revision::DELETED_USER + # to put a 1 in the username *_deleted bit. To unsuppress we AND the + # current bitfields with the inverse of Revision::DELETED_USER. The + # username bit is made to 0 (x & 0 = 0), while others are unchanged (x & 1 = x). + # The same goes for the sysop-restricted *_deleted bit. + if( $op == '&' ) { + $delUser = "~{$delUser}"; + $delAction = "~{$delAction}"; + } + # Hide name from live edits + $dbw->update( 'revision', array("rev_deleted = rev_deleted $op $delUser"), + array('rev_user' => $userId), __METHOD__ ); + # Hide name from deleted edits + $dbw->update( 'archive', array("ar_deleted = ar_deleted $op $delUser"), + array('ar_user_text' => $name), __METHOD__ ); + # Hide name from logs + $dbw->update( 'logging', array("log_deleted = log_deleted $op $delUser"), + array('log_user' => $userId, "log_type != 'suppress'"), __METHOD__ ); + $dbw->update( 'logging', array("log_deleted = log_deleted $op $delAction"), + array('log_namespace' => NS_USER, 'log_title' => $userDbKey, + "log_type != 'suppress'"), __METHOD__ ); + # Hide name from RC + $dbw->update( 'recentchanges', array("rc_deleted = rc_deleted $op $delUser"), + array('rc_user_text' => $name), __METHOD__ ); + # Hide name from live images + $dbw->update( 'oldimage', array("oi_deleted = oi_deleted $op $delUser"), + array('oi_user_text' => $name), __METHOD__ ); + # Hide name from deleted images + # WMF - schema change pending + # $dbw->update( 'filearchive', array("fa_deleted = fa_deleted $op $delUser"), + # array('fa_user_text' => $name), __METHOD__ ); + # Done! + return true; } /** @@ -487,7 +592,7 @@ class IPBlockForm { global $wgBlockAllowsUTEdit; $flags = array(); if( $this->BlockAnonOnly && IP::isIPAddress( $this->BlockAddress ) ) - // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log + // when blocking a user the option 'anononly' is not available/has no effect -> do not write this into log $flags[] = 'anononly'; if( $this->BlockCreateAccount ) $flags[] = 'nocreate'; @@ -497,6 +602,8 @@ class IPBlockForm { $flags[] = 'noemail'; if ( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit ) $flags[] = 'nousertalk'; + if ( $this->BlockHideName ) + $flags[] = 'hiddenname'; return implode( ',', $flags ); } @@ -506,14 +613,14 @@ class IPBlockForm { * @return string */ private function getConvenienceLinks() { - global $wgUser; + global $wgUser, $wgLang; $skin = $wgUser->getSkin(); if( $this->BlockAddress ) $links[] = $this->getContribsLink( $skin ); $links[] = $this->getUnblockLink( $skin ); $links[] = $this->getBlockListLink( $skin ); $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) ); - return '<p class="mw-ipb-conveniencelinks">' . implode( ' | ', $links ) . '</p>'; + return '<p class="mw-ipb-conveniencelinks">' . $wgLang->pipeList( $links ) . '</p>'; } /** diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index 12b119d8..db466c14 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -34,7 +34,7 @@ class SpecialBookSources extends SpecialPage { $wgOut->addWikiMsg( 'booksources-summary' ); $wgOut->addHTML( $this->makeForm() ); if( strlen( $this->isbn ) > 0 ) { - if( !$this->isValidIsbn( $this->isbn ) ) { + if( !self::isValidISBN( $this->isbn ) ) { $wgOut->wrapWikiMsg( '<div class="error">$1</div>', 'booksources-invalid-isbn' ); } $this->showList(); diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index 8c2ae2b6..c6e73f2b 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -44,7 +44,6 @@ class CategoryPager extends AlphabeticPager { } function getQueryInfo() { - global $wgRequest; return array( 'tables' => array( 'category' ), 'fields' => array( 'cat_title','cat_pages' ), @@ -61,6 +60,7 @@ class CategoryPager extends AlphabeticPager { function getDefaultQuery() { parent::getDefaultQuery(); unset( $this->mDefaultQuery['from'] ); + return $this->mDefaultQuery; } # protected function getOrderTypeMessages() { # return array( 'abc' => 'special-categories-sort-abc', diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index e19aa99b..9c6f857d 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -68,13 +68,13 @@ class EmailConfirmation extends UnlistedSpecialPage { $wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t ); } if( $wgUser->isEmailConfirmationPending() ) { - $wgOut->addWikiMsg( 'confirmemail_pending' ); + $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">$1</div>", 'confirmemail_pending' ); } $wgOut->addWikiMsg( 'confirmemail_text' ); $self = SpecialPage::getTitleFor( 'Confirmemail' ); $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) ); $form .= Xml::hidden( 'token', $wgUser->editToken() ); - $form .= Xml::submitButton( wfMsgHtml( 'confirmemail_send' ) ); + $form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) ); $form .= Xml::closeElement( 'form' ); $wgOut->addHTML( $form ); } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 3d8c18dd..9263336e 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -35,7 +35,7 @@ class SpecialContributions extends SpecialPage { } if( !strlen( $target ) ) { - $wgOut->addHTML( $this->getForm( '' ) ); + $wgOut->addHTML( $this->getForm() ); return; } @@ -44,7 +44,7 @@ class SpecialContributions extends SpecialPage { $nt = Title::makeTitleSafe( NS_USER, $target ); if( !$nt ) { - $wgOut->addHTML( $this->getForm( '' ) ); + $wgOut->addHTML( $this->getForm() ); return; } $id = User::idFromName( $nt->getText() ); @@ -52,7 +52,7 @@ class SpecialContributions extends SpecialPage { if( $target != 'newbies' ) { $target = $nt->getText(); $wgOut->setSubtitle( $this->contributionsSub( $nt, $id ) ); - $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'contributions-title', $target ) ) ); + $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsgExt( 'contributions-title', array( 'parsemag' ),$target ) ) ); } else { $wgOut->setSubtitle( wfMsgHtml( 'sp-contributions-newbies-sub') ); $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'sp-contributions-newbies-title' ) ) ); @@ -63,6 +63,8 @@ class SpecialContributions extends SpecialPage { } else { $this->opts['namespace'] = ''; } + + $this->opts['tagfilter'] = (string) $wgRequest->getVal( 'tagfilter' ); // Allows reverts to have the bot flag in recent changes. It is just here to // be passed in the form at the top of the page @@ -72,27 +74,12 @@ class SpecialContributions extends SpecialPage { $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; # Offset overrides year/month selection - if( ( $month = $wgRequest->getIntOrNull( 'month' ) ) !== null && $month !== -1 ) { - $this->opts['month'] = intval( $month ); - } else { - $this->opts['month'] = ''; - } - if( ( $year = $wgRequest->getIntOrNull( 'year' ) ) !== null ) { - $this->opts['year'] = intval( $year ); - } else if( $this->opts['month'] ) { - $thisMonth = intval( gmdate( 'n' ) ); - $thisYear = intval( gmdate( 'Y' ) ); - if( intval( $this->opts['month'] ) > $thisMonth ) { - $thisYear--; - } - $this->opts['year'] = $thisYear; - } else { - $this->opts['year'] = ''; - } - if( $skip ) { $this->opts['year'] = ''; $this->opts['month'] = ''; + } else { + $this->opts['year'] = $wgRequest->getIntOrNull( 'year' ); + $this->opts['month'] = $wgRequest->getIntOrNull( 'month' ); } // Add RSS/atom links @@ -104,11 +91,11 @@ class SpecialContributions extends SpecialPage { wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id ); - $wgOut->addHTML( $this->getForm( $this->opts ) ); + $wgOut->addHTML( $this->getForm() ); $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] ); if( !$pager->getNumRows() ) { - $wgOut->addWikiMsg( 'nocontribs' ); + $wgOut->addWikiMsg( 'nocontribs', $target ); return; } @@ -177,18 +164,27 @@ class SpecialContributions extends SpecialPage { wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() ); } # Other logs link - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsgHtml( 'log' ), - 'user=' . $nt->getPartialUrl() ); + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsg( 'sp-contributions-logs' ), + 'user=' . $nt->getPartialUrl() ); # Add link to deleted user contributions for priviledged users if( $wgUser->isAllowed( 'deletedhistory' ) ) { - $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'DeletedContributions', + $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'DeletedContributions', $nt->getDBkey() ), wfMsgHtml( 'deletedcontributions' ) ); } - + + # Add a link to change user rights for privileged users + $userrightsPage = new UserrightsPage(); + if( 0 !== $id && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) { + $tools[] = $sk->makeKnownLinkObj( + SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ), + wfMsgHtml( 'userrights' ) + ); + } + wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - $links = implode( ' | ', $tools ); + $links = $wgLang->pipeList( $tools ); } // Old message 'contribsub' had one parameter, but that doesn't work for @@ -235,15 +231,21 @@ class SpecialContributions extends SpecialPage { if( $this->opts['contribs'] == 'newbie' ) { $this->opts['target'] = ''; } + + if( !isset( $this->opts['tagfilter'] ) ) { + $this->opts['tagfilter'] = ''; + } $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - + # Add hidden params for tracking foreach ( $this->opts as $name => $value ) { if( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { continue; } $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; } + + $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); $f .= '<fieldset>' . Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . @@ -256,16 +258,11 @@ class SpecialContributions extends SpecialPage { Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . Xml::namespaceSelector( $this->opts['namespace'], '' ) . '</span>' . + ( $tagFilter ? Xml::tags( 'p', null, implode( ' ', $tagFilter ) ) : '' ) . Xml::openElement( 'p' ) . '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'year' ), 'year' ) . ' '. - Xml::input( 'year', 4, $this->opts['year'], array('id' => 'year', 'maxlength' => 4) ) . - '</span>' . - ' '. - '<span style="white-space: nowrap">' . - Xml::label( wfMsg( 'month' ), 'month' ) . ' '. - Xml::monthSelector( $this->opts['month'], -1 ) . ' '. - '</span>' . + Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . + '</span>' . ' ' . Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . Xml::closeElement( 'p' ); @@ -278,7 +275,7 @@ class SpecialContributions extends SpecialPage { return $f; } - /** + /** * Output a subscription feed listing recent edits to this page. * @param string $type */ @@ -300,14 +297,15 @@ class SpecialContributions extends SpecialPage { $feed = new $wgFeedClasses[$type]( $this->feedTitle(), wfMsgExt( 'tagline', 'parsemag' ), - $this->getTitle()->getFullUrl() ); + $this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target']) + ); // Already valid title $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] ); $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText(); $pager = new ContribsPager( $target, $this->opts['namespace'], - $this->opts['year'], $this->opts['month'] ); + $this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] ); $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit ); @@ -353,7 +351,7 @@ class SpecialContributions extends SpecialPage { protected function feedItemDesc( $revision ) { if( $revision ) { - return '<p>' . htmlspecialchars( $revision->getUserText() ) . ': ' . + return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; @@ -371,13 +369,14 @@ class ContribsPager extends ReverseChronologicalPager { var $messages, $target; var $namespace = '', $mDb; - function __construct( $target, $namespace = false, $year = false, $month = false ) { + function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) { parent::__construct(); foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) { $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') ); } $this->target = $target; $this->namespace = $namespace; + $this->tagFilter = $tagFilter; $this->getDateCond( $year, $month ); @@ -391,19 +390,35 @@ class ContribsPager extends ReverseChronologicalPager { } function getQueryInfo() { + global $wgUser; list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); - $conds = array_merge( array('page_id=rev_page'), $userCond, $this->getNamespaceCond() ); + + $conds = array_merge( $userCond, $this->getNamespaceCond() ); + // Paranoia: avoid brute force searches (bug 17342) + if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + $conds[] = 'rev_deleted & ' . Revision::DELETED_USER . ' = 0'; + } + $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' ); + $queryInfo = array( 'tables' => $tables, 'fields' => array( - 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'rev_id', 'rev_page', - 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', - 'rev_user_text', 'rev_parent_id', 'rev_deleted' + 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect', + 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment', + 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted' ), 'conds' => $conds, 'options' => array( 'USE INDEX' => array('revision' => $index) ), 'join_conds' => $join_cond ); + + ChangeTags::modifyDisplayQuery( $queryInfo['tables'], + $queryInfo['fields'], + $queryInfo['conds'], + $queryInfo['join_conds'], + $queryInfo['options'], + $this->tagFilter ); + wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) ); return $queryInfo; } @@ -458,36 +473,38 @@ class ContribsPager extends ReverseChronologicalPager { * @todo This would probably look a lot nicer in a table. */ function formatRow( $row ) { - wfProfileIn( __METHOD__ ); - global $wgLang, $wgUser, $wgContLang; + wfProfileIn( __METHOD__ ); $sk = $this->getSkin(); $rev = new Revision( $row ); + $classes = array(); - $page = Title::makeTitle( $row->page_namespace, $row->page_title ); - $link = $sk->makeKnownLinkObj( $page ); + $page = Title::newFromRow( $row ); + $page->resetArticleId( $row->rev_page ); // use process cache + $link = $sk->makeLinkObj( $page, $page->getPrefixedText(), $page->isRedirect() ? 'redirect=no' : '' ); + # Mark current revisions $difftext = $topmarktext = ''; if( $row->rev_id == $row->page_latest ) { $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>'; if( !$row->page_is_new ) { $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')'; + # Add rollback link + if( $page->quickUserCan( 'rollback') && $page->quickUserCan( 'edit' ) ) { + $topmarktext .= ' '.$sk->generateRollback( $rev ); + } } else { $difftext .= $this->messages['newarticle']; } - - if( $page->userCan( 'rollback') && $page->userCan( 'edit' ) ) { - $topmarktext .= ' '.$sk->generateRollback( $rev ); - } - } # Is there a visible previous revision? if( $rev->userCan(Revision::DELETED_TEXT) ) { - $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=prev&oldid='.$row->rev_id ) . ')'; + $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], + 'diff=prev&oldid='.$row->rev_id ) . ')'; } else { $difftext = '(' . $this->messages['diff'] . ')'; } - $histlink='('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; + $histlink = '('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')'; $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true ); $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); @@ -510,7 +527,7 @@ class ContribsPager extends ReverseChronologicalPager { $nflag = ''; } - if( $row->rev_minor_edit ) { + if( $rev->isMinor() ) { $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> '; } else { $mflag = ''; @@ -520,10 +537,17 @@ class ContribsPager extends ReverseChronologicalPager { if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { $ret .= ' ' . wfMsgHtml( 'deletedrev' ); } + + # Tags, if any. + list($tagSummary, $newClasses) = ChangeTags::formatSummaryRow( $row->ts_tags, 'contributions' ); + $classes = array_merge( $classes, $newClasses ); + $ret .= " $tagSummary"; + // Let extensions add data wfRunHooks( 'ContributionsLineEnding', array( &$this, &$ret, $row ) ); - - $ret = "<li>$ret</li>\n"; + + $classes = implode( ' ', $classes ); + $ret = "<li class=\"$classes\">$ret</li>\n"; wfProfileOut( __METHOD__ ); return $ret; } diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php index 513d25e2..67b05ca1 100644 --- a/includes/specials/SpecialDeletedContributions.php +++ b/includes/specials/SpecialDeletedContributions.php @@ -26,9 +26,13 @@ class DeletedContribsPager extends IndexPager { } function getQueryInfo() { + global $wgUser; list( $index, $userCond ) = $this->getUserCond(); $conds = array_merge( $userCond, $this->getNamespaceCond() ); - + // Paranoia: avoid brute force searches (bug 17792) + if( !$wgUser->isAllowed( 'suppressrevision' ) ) { + $conds[] = 'ar_deleted & ' . Revision::DELETED_USER . ' = 0'; + } return array( 'tables' => array( 'archive' ), 'fields' => array( @@ -36,7 +40,7 @@ class DeletedContribsPager extends IndexPager { 'ar_user', 'ar_user_text', 'ar_deleted' ), 'conds' => $conds, - 'options' => array( 'FORCE INDEX' => $index ) + 'options' => array( 'USE INDEX' => $index ) ); } @@ -62,6 +66,8 @@ class DeletedContribsPager extends IndexPager { } function getNavigationBar() { + global $wgLang; + if ( isset( $this->mNavigationBar ) ) { return $this->mNavigationBar; } @@ -74,9 +80,9 @@ class DeletedContribsPager extends IndexPager { $pagingLinks = $this->getPagingLinks( $linkTexts ); $limitLinks = $this->getLimitLinks(); - $limits = implode( ' | ', $limitLinks ); + $limits = $wgLang->pipeList( $limitLinks ); - $this->mNavigationBar = "({$pagingLinks['first']} | {$pagingLinks['last']}) " . + $this->mNavigationBar = "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " . wfMsgExt( 'viewprevnext', array( 'parsemag' ), $pagingLinks['prev'], $pagingLinks['next'], $limits ); return $this->mNavigationBar; } @@ -113,7 +119,7 @@ class DeletedContribsPager extends IndexPager { 'user_text' => $row->ar_user_text, 'timestamp' => $row->ar_timestamp, 'minor_edit' => $row->ar_minor_edit, - 'rev_deleted' => $row->ar_deleted, + 'deleted' => $row->ar_deleted, ) ); $page = Title::makeTitle( $row->ar_namespace, $row->ar_title ); @@ -204,6 +210,8 @@ class DeletedContributionsPage extends SpecialPage { global $wgUser, $wgOut, $wgLang, $wgRequest; + $wgOut->setPageTitle( wfMsgExt( 'deletedcontributions-title', array( 'parsemag' ) ) ); + $options = array(); if ( isset( $par ) ) { @@ -306,7 +314,7 @@ class DeletedContributionsPage extends SpecialPage { wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) ); - $links = implode( ' | ', $tools ); + $links = $wgLang->pipeList( $tools ); } // Old message 'contribsub' had one parameter, but that doesn't work for diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index cf90f94d..58e2514e 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -241,7 +241,7 @@ class EmailUserForm { $user = $this->target; $wgOut->setPagetitle( wfMsg( "emailsent" ) ); - $wgOut->addHTML( wfMsg( "emailsenttext" ) ); + $wgOut->addWikiMsg( 'emailsenttext' ); $wgOut->returnToMain( false, $user->getUserPage() ); } diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php index 898b5a78..8bf16a71 100644 --- a/includes/specials/SpecialExport.php +++ b/includes/specials/SpecialExport.php @@ -21,208 +21,197 @@ * @ingroup SpecialPage */ -function wfExportGetPagesFromCategory( $title ) { - global $wgContLang; - - $name = $title->getDBkey(); - - $dbr = wfGetDB( DB_SLAVE ); - - list( $page, $categorylinks ) = $dbr->tableNamesN( 'page', 'categorylinks' ); - $sql = "SELECT page_namespace, page_title FROM $page " . - "JOIN $categorylinks ON cl_from = page_id " . - "WHERE cl_to = " . $dbr->addQuotes( $name ); - - $pages = array(); - $res = $dbr->query( $sql, 'wfExportGetPagesFromCategory' ); - while ( $row = $dbr->fetchObject( $res ) ) { - $n = $row->page_title; - if ($row->page_namespace) { - $ns = $wgContLang->getNsText( $row->page_namespace ); - $n = $ns . ':' . $n; - } - - $pages[] = $n; +class SpecialExport extends SpecialPage { + + private $curonly, $doExport, $pageLinkDepth, $templates; + private $images; + + public function __construct() { + parent::__construct( 'Export' ); } - $dbr->freeResult($res); - - return $pages; -} - -/** - * Expand a list of pages to include templates used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetTemplates( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'templatelinks', - array( 'tl_namespace AS namespace', 'tl_title AS title' ), - array( 'page_id=tl_from' ) ); -} - -/** - * Expand a list of pages to include images used in those pages. - * @param $inputPages array, list of titles to look up - * @param $pageSet array, associative array indexed by titles for output - * @return array associative array index by titles - */ -function wfExportGetImages( $inputPages, $pageSet ) { - return wfExportGetLinks( $inputPages, $pageSet, - 'imagelinks', - array( NS_FILE . ' AS namespace', 'il_to AS title' ), - array( 'page_id=il_from' ) ); -} - -/** - * Expand a list of pages to include items used in those pages. - * @private - */ -function wfExportGetLinks( $inputPages, $pageSet, $table, $fields, $join ) { - $dbr = wfGetDB( DB_SLAVE ); - foreach( $inputPages as $page ) { - $title = Title::newFromText( $page ); - if( $title ) { - $pageSet[$title->getPrefixedText()] = true; - /// @fixme May or may not be more efficient to batch these - /// by namespace when given multiple input pages. - $result = $dbr->select( - array( 'page', $table ), - $fields, - array_merge( $join, - array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBKey() ) ), - __METHOD__ ); - foreach( $result as $row ) { - $template = Title::makeTitle( $row->namespace, $row->title ); - $pageSet[$template->getPrefixedText()] = true; + + public function execute( $par ) { + global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; + global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth; + global $wgExportFromNamespaces; + + $this->setHeaders(); + $this->outputHeader(); + + // Set some variables + $this->curonly = true; + $this->doExport = false; + $this->templates = $wgRequest->getCheck( 'templates' ); + $this->images = $wgRequest->getCheck( 'images' ); // Doesn't do anything yet + $this->pageLinkDepth = $this->validateLinkDepth( + $wgRequest->getIntOrNull( 'pagelink-depth' ) ); + + if ( $wgRequest->getCheck( 'addcat' ) ) { + $page = $wgRequest->getText( 'pages' ); + $catname = $wgRequest->getText( 'catname' ); + + if ( $catname !== '' && $catname !== NULL && $catname !== false ) { + $t = Title::makeTitleSafe( NS_MAIN, $catname ); + if ( $t ) { + /** + * @fixme This can lead to hitting memory limit for very large + * categories. Ideally we would do the lookup synchronously + * during the export in a single query. + */ + $catpages = $this->getPagesFromCategory( $t ); + if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); + } } } - } - return $pageSet; -} - -/** - * Callback function to remove empty strings from the pages array. - */ -function wfFilterPage( $page ) { - return $page !== '' && $page !== null; -} - -/** - * - */ -function wfSpecialExport( $page = '' ) { - global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; - global $wgExportAllowHistory, $wgExportMaxHistory; - - $curonly = true; - $doexport = false; - - if ( $wgRequest->getCheck( 'addcat' ) ) { - $page = $wgRequest->getText( 'pages' ); - $catname = $wgRequest->getText( 'catname' ); - - if ( $catname !== '' && $catname !== NULL && $catname !== false ) { - $t = Title::makeTitleSafe( NS_MAIN, $catname ); - if ( $t ) { + else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) { + $page = $wgRequest->getText( 'pages' ); + $nsindex = $wgRequest->getText( 'nsindex' ); + + if ( $nsindex !== '' && $nsindex !== NULL && $nsindex !== false ) { /** - * @fixme This can lead to hitting memory limit for very large - * categories. Ideally we would do the lookup synchronously - * during the export in a single query. + * Same implementation as above, so same @fixme */ - $catpages = wfExportGetPagesFromCategory( $t ); - if ( $catpages ) $page .= "\n" . implode( "\n", $catpages ); - } + $nspages = $this->getPagesFromNamespace( $nsindex ); + if ( $nspages ) $page .= "\n" . implode( "\n", $nspages ); + } } - } - else if( $wgRequest->wasPosted() && $page == '' ) { - $page = $wgRequest->getText( 'pages' ); - $curonly = $wgRequest->getCheck( 'curonly' ); - $rawOffset = $wgRequest->getVal( 'offset' ); - if( $rawOffset ) { - $offset = wfTimestamp( TS_MW, $rawOffset ); + else if( $wgRequest->wasPosted() && $par == '' ) { + $page = $wgRequest->getText( 'pages' ); + $this->curonly = $wgRequest->getCheck( 'curonly' ); + $rawOffset = $wgRequest->getVal( 'offset' ); + if( $rawOffset ) { + $offset = wfTimestamp( TS_MW, $rawOffset ); + } else { + $offset = null; + } + $limit = $wgRequest->getInt( 'limit' ); + $dir = $wgRequest->getVal( 'dir' ); + $history = array( + 'dir' => 'asc', + 'offset' => false, + 'limit' => $wgExportMaxHistory, + ); + $historyCheck = $wgRequest->getCheck( 'history' ); + if ( $this->curonly ) { + $history = WikiExporter::CURRENT; + } elseif ( !$historyCheck ) { + if ( $limit > 0 && $limit < $wgExportMaxHistory ) { + $history['limit'] = $limit; + } + if ( !is_null( $offset ) ) { + $history['offset'] = $offset; + } + if ( strtolower( $dir ) == 'desc' ) { + $history['dir'] = 'desc'; + } + } + + if( $page != '' ) $this->doExport = true; } else { - $offset = null; + // Default to current-only for GET requests + $page = $wgRequest->getText( 'pages', $par ); + $historyCheck = $wgRequest->getCheck( 'history' ); + if( $historyCheck ) { + $history = WikiExporter::FULL; + } else { + $history = WikiExporter::CURRENT; + } + + if( $page != '' ) $this->doExport = true; } - $limit = $wgRequest->getInt( 'limit' ); - $dir = $wgRequest->getVal( 'dir' ); - $history = array( - 'dir' => 'asc', - 'offset' => false, - 'limit' => $wgExportMaxHistory, - ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if ( $curonly ) { + + if( !$wgExportAllowHistory ) { + // Override $history = WikiExporter::CURRENT; - } elseif ( !$historyCheck ) { - if ( $limit > 0 && $limit < $wgExportMaxHistory ) { - $history['limit'] = $limit; - } - if ( !is_null( $offset ) ) { - $history['offset'] = $offset; - } - if ( strtolower( $dir ) == 'desc' ) { - $history['dir'] = 'desc'; + } + + $list_authors = $wgRequest->getCheck( 'listauthors' ); + if ( !$this->curonly || !$wgExportAllowListContributors ) $list_authors = false ; + + if ( $this->doExport ) { + $wgOut->disable(); + // Cancel output buffering and gzipping if set + // This should provide safer streaming for pages with history + wfResetOutputBuffers(); + header( "Content-type: application/xml; charset=utf-8" ); + if( $wgRequest->getCheck( 'wpDownload' ) ) { + // Provide a sane filename suggestion + $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); + $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); } + $this->doExport( $page, $history, $list_authors ); + return; } - - if( $page != '' ) $doexport = true; - } else { - // Default to current-only for GET requests - $page = $wgRequest->getText( 'pages', $page ); - $historyCheck = $wgRequest->getCheck( 'history' ); - if( $historyCheck ) { - $history = WikiExporter::FULL; + + $wgOut->addWikiMsg( 'exporttext' ); + + $form = Xml::openElement( 'form', array( 'method' => 'post', + 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) ); + $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; + $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />'; + + if ( $wgExportFromNamespaces ) { + $form .= Xml::namespaceSelector( '', null, 'nsindex', wfMsg( 'export-addnstext' ) ) . ' '; + $form .= Xml::submitButton( wfMsg( 'export-addns' ), array( 'name' => 'addns' ) ) . '<br />'; + } + + $form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false ); + $form .= '<br />'; + + if( $wgExportAllowHistory ) { + $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; } else { - $history = WikiExporter::CURRENT; + $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) ); } - - if( $page != '' ) $doexport = true; + $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; + if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) { + $form .= Xml::inputLabel( wfMsg( 'export-pagelinks' ), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />'; + } + // Enable this when we can do something useful exporting/importing image information. :) + //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; + $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; + + $form .= Xml::submitButton( wfMsg( 'export-submit' ), array( 'accesskey' => 's' ) ); + $form .= Xml::closeElement( 'form' ); + $wgOut->addHTML( $form ); } + + private function userCanOverrideExportDepth() { + global $wgUser; - if( !$wgExportAllowHistory ) { - // Override - $history = WikiExporter::CURRENT; + return $wgUser->isAllowed( 'override-export-depth' ); } - - $list_authors = $wgRequest->getCheck( 'listauthors' ); - if ( !$curonly || !$wgExportAllowListContributors ) $list_authors = false ; - - if ( $doexport ) { - $wgOut->disable(); - - // Cancel output buffering and gzipping if set - // This should provide safer streaming for pages with history - wfResetOutputBuffers(); - header( "Content-type: application/xml; charset=utf-8" ); - if( $wgRequest->getCheck( 'wpDownload' ) ) { - // Provide a sane filename suggestion - $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); - $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); - } - + + /** + * Do the actual page exporting + * @param string $page User input on what page(s) to export + * @param mixed $history one of the WikiExporter history export constants + */ + private function doExport( $page, $history, $list_authors ) { + global $wgExportMaxHistory; + /* Split up the input and look up linked pages */ - $inputPages = array_filter( explode( "\n", $page ), 'wfFilterPage' ); + $inputPages = array_filter( explode( "\n", $page ), array( $this, 'filterPage' ) ); $pageSet = array_flip( $inputPages ); - - if( $wgRequest->getCheck( 'templates' ) ) { - $pageSet = wfExportGetTemplates( $inputPages, $pageSet ); + + if( $this->templates ) { + $pageSet = $this->getTemplates( $inputPages, $pageSet ); } - - /* - // Enable this when we can do something useful exporting/importing image information. :) - if( $wgRequest->getCheck( 'images' ) ) { - $pageSet = wfExportGetImages( $inputPages, $pageSet ); + + if( $linkDepth = $this->pageLinkDepth ) { + $pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth ); } - */ - + + /* + // Enable this when we can do something useful exporting/importing image information. :) + if( $this->images ) ) { + $pageSet = $this->getImages( $inputPages, $pageSet ); + } + */ + $pages = array_keys( $pageSet ); - + /* Ok, let's get to it... */ - if( $history == WikiExporter::CURRENT ) { $lb = false; $db = wfGetDB( DB_SLAVE ); @@ -238,65 +227,177 @@ function wfSpecialExport( $page = '' ) { set_time_limit(0); wfRestoreWarnings(); } - $exporter = new WikiExporter( $db, $history, $buffer ); - $exporter->list_authors = $list_authors ; + $exporter->list_authors = $list_authors; $exporter->openStream(); - foreach( $pages as $page ) { /* - if( $wgExportMaxHistory && !$curonly ) { - $title = Title::newFromText( $page ); - if( $title ) { - $count = Revision::countByTitle( $db, $title ); - if( $count > $wgExportMaxHistory ) { - wfDebug( __FUNCTION__ . - ": Skipped $page, $count revisions too big\n" ); - continue; - } - } - }*/ - + if( $wgExportMaxHistory && !$this->curonly ) { + $title = Title::newFromText( $page ); + if( $title ) { + $count = Revision::countByTitle( $db, $title ); + if( $count > $wgExportMaxHistory ) { + wfDebug( __FUNCTION__ . + ": Skipped $page, $count revisions too big\n" ); + continue; + } + } + }*/ #Bug 8824: Only export pages the user can read $title = Title::newFromText( $page ); if( is_null( $title ) ) continue; #TODO: perhaps output an <error> tag or something. if( !$title->userCanRead() ) continue; #TODO: perhaps output an <error> tag or something. - + $exporter->pageByTitle( $title ); } - + $exporter->closeStream(); if( $lb ) { $lb->closeAll(); } - return; } - - $self = SpecialPage::getTitleFor( 'Export' ); - $wgOut->addHTML( wfMsgExt( 'exporttext', 'parse' ) ); - - $form = Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $self->getLocalUrl( 'action=submit' ) ) ); - - $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; - $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />'; - - $form .= Xml::openElement( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ) ); - $form .= htmlspecialchars( $page ); - $form .= Xml::closeElement( 'textarea' ); - $form .= '<br />'; - - if( $wgExportAllowHistory ) { - $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; - } else { - $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) ); + + + private function getPagesFromCategory( $title ) { + global $wgContLang; + + $name = $title->getDBkey(); + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( array('page', 'categorylinks' ), + array( 'page_namespace', 'page_title' ), + array('cl_from=page_id', 'cl_to' => $name ), + __METHOD__, array('LIMIT' => '5000')); + + $pages = array(); + while ( $row = $dbr->fetchObject( $res ) ) { + $n = $row->page_title; + if ($row->page_namespace) { + $ns = $wgContLang->getNsText( $row->page_namespace ); + $n = $ns . ':' . $n; + } + + $pages[] = $n; + } + $dbr->freeResult($res); + + return $pages; + } + + private function getPagesFromNamespace( $nsindex ) { + global $wgContLang; + + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'page', array('page_namespace', 'page_title'), + array('page_namespace' => $nsindex), + __METHOD__, array('LIMIT' => '5000') ); + + $pages = array(); + while ( $row = $dbr->fetchObject( $res ) ) { + $n = $row->page_title; + if ($row->page_namespace) { + $ns = $wgContLang->getNsText( $row->page_namespace ); + $n = $ns . ':' . $n; + } + + $pages[] = $n; + } + $dbr->freeResult($res); + + return $pages; + } + /** + * Expand a list of pages to include templates used in those pages. + * @param $inputPages array, list of titles to look up + * @param $pageSet array, associative array indexed by titles for output + * @return array associative array index by titles + */ + private function getTemplates( $inputPages, $pageSet ) { + return $this->getLinks( $inputPages, $pageSet, + 'templatelinks', + array( 'tl_namespace AS namespace', 'tl_title AS title' ), + array( 'page_id=tl_from' ) ); + } + + /** + * Validate link depth setting, if available. + */ + private function validateLinkDepth( $depth ) { + global $wgExportMaxLinkDepth, $wgExportMaxLinkDepthLimit; + if( $depth < 0 ) { + return 0; + } + if ( !$this->userCanOverrideExportDepth() ) { + if( $depth > $wgExportMaxLinkDepth ) { + return $wgExportMaxLinkDepth; + } + } + /* + * There's a HARD CODED limit of 5 levels of recursion here to prevent a + * crazy-big export from being done by someone setting the depth + * number too high. In other words, last resort safety net. + */ + return intval( min( $depth, 5 ) ); + } + + /** Expand a list of pages to include pages linked to from that page. */ + private function getPageLinks( $inputPages, $pageSet, $depth ) { + for( $depth=$depth; $depth>0; --$depth ) { + $pageSet = $this->getLinks( $inputPages, $pageSet, 'pagelinks', + array( 'pl_namespace AS namespace', 'pl_title AS title' ), + array( 'page_id=pl_from' ) ); + $inputPages = array_keys( $pageSet ); + } + return $pageSet; + } + + /** + * Expand a list of pages to include images used in those pages. + * @param $inputPages array, list of titles to look up + * @param $pageSet array, associative array indexed by titles for output + * @return array associative array index by titles + */ + private function getImages( $inputPages, $pageSet ) { + return $this->getLinks( $inputPages, $pageSet, + 'imagelinks', + array( NS_FILE . ' AS namespace', 'il_to AS title' ), + array( 'page_id=il_from' ) ); + } + + /** + * Expand a list of pages to include items used in those pages. + * @private + */ + private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) { + $dbr = wfGetDB( DB_SLAVE ); + foreach( $inputPages as $page ) { + $title = Title::newFromText( $page ); + if( $title ) { + $pageSet[$title->getPrefixedText()] = true; + /// @fixme May or may not be more efficient to batch these + /// by namespace when given multiple input pages. + $result = $dbr->select( + array( 'page', $table ), + $fields, + array_merge( $join, + array( + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDBKey() ) ), + __METHOD__ ); + foreach( $result as $row ) { + $template = Title::makeTitle( $row->namespace, $row->title ); + $pageSet[$template->getPrefixedText()] = true; + } + } + } + return $pageSet; + } + + /** + * Callback function to remove empty strings from the pages array. + */ + private function filterPage( $page ) { + return $page !== '' && $page !== null; } - $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; - // Enable this when we can do something useful exporting/importing image information. :) - //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; - $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; - - $form .= Xml::submitButton( wfMsg( 'export-submit' ), array( 'accesskey' => 's' ) ); - $form .= Xml::closeElement( 'form' ); - $wgOut->addHTML( $form ); } + diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php index 49a218c8..4fde0a60 100644 --- a/includes/specials/SpecialFileDuplicateSearch.php +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -64,7 +64,7 @@ class FileDuplicateSearchPage extends QueryPage { * Output the HTML search form, and constructs the FileDuplicateSearch object. */ function wfSpecialFileDuplicateSearch( $par = null ) { - global $wgRequest, $wgTitle, $wgOut, $wgLang, $wgContLang; + global $wgRequest, $wgOut, $wgLang, $wgContLang, $wgScript; $hash = ''; $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); @@ -85,7 +85,8 @@ function wfSpecialFileDuplicateSearch( $par = null ) { # Create the input form $wgOut->addHTML( - Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) . + Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) . + Xml::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' . diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 5e1a6533..457e03b4 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -30,6 +30,7 @@ class SpecialImport extends SpecialPage { private $frompage = ''; private $logcomment= false; private $history = true; + private $includeTemplates = false; /** * Constructor @@ -65,12 +66,13 @@ class SpecialImport extends SpecialPage { * Do the actual import */ private function doImport() { - global $wgOut, $wgRequest, $wgUser, $wgImportSources; + global $wgOut, $wgRequest, $wgUser, $wgImportSources, $wgExportMaxLinkDepth; $isUpload = false; $this->namespace = $wgRequest->getIntOrNull( 'namespace' ); $sourceName = $wgRequest->getVal( "source" ); $this->logcomment = $wgRequest->getText( 'log-comment' ); + $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $wgRequest->getIntOrNull( 'pagelink-depth' ); if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) { $source = new WikiErrorMsg( 'import-token-mismatch' ); @@ -88,10 +90,13 @@ class SpecialImport extends SpecialPage { } else { $this->history = $wgRequest->getCheck( 'interwikiHistory' ); $this->frompage = $wgRequest->getText( "frompage" ); + $this->includeTemplates = $wgRequest->getCheck( 'interwikiTemplates' ); $source = ImportStreamSource::newFromInterwiki( $this->interwiki, $this->frompage, - $this->history ); + $this->history, + $this->includeTemplates, + $this->pageLinkDepth ); } } else { $source = new WikiErrorMsg( "importunknownsource" ); @@ -127,9 +132,7 @@ class SpecialImport extends SpecialPage { } private function showForm() { - global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources; - # FIXME: Quick hack to disable import for non privileged users /Raymond - # Regression from 43963 + global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources, $wgExportMaxLinkDepth; if( !$wgUser->isAllowed( 'import' ) && !$wgUser->isAllowed( 'importupload' ) ) return $wgOut->permissionRequired( 'import' ); @@ -138,9 +141,9 @@ class SpecialImport extends SpecialPage { if( $wgUser->isAllowed( 'importupload' ) ) { $wgOut->addWikiMsg( "importtext" ); $wgOut->addHTML( - Xml::openElement( 'fieldset' ). - Xml::element( 'legend', null, wfMsg( 'import-upload' ) ) . - Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', 'action' => $action ) ) . + Xml::fieldset( wfMsg( 'import-upload' ) ). + Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', + 'action' => $action, 'id' => 'mw-import-upload-form' ) ) . Xml::hidden( 'action', 'submit' ) . Xml::hidden( 'source', 'upload' ) . Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . @@ -164,7 +167,7 @@ class SpecialImport extends SpecialPage { </tr> <tr> <td></td> - <td class='mw-input'>" . + <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'uploadbtn' ) ) . "</td> </tr>" . @@ -180,10 +183,22 @@ class SpecialImport extends SpecialPage { } if( $wgUser->isAllowed( 'import' ) && !empty( $wgImportSources ) ) { + # Show input field for import depth only if $wgExportMaxLinkDepth > 0 + $importDepth = ''; + if( $wgExportMaxLinkDepth > 0 ) { + $importDepth = "<tr> + <td class='mw-label'>" . + wfMsgExt( 'export-pagelinks', 'parseinline' ) . + "</td> + <td class='mw-input'>" . + Xml::input( 'pagelink-depth', 3, 0 ) . + "</td> + </tr>"; + } + $wgOut->addHTML( - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'importinterwiki' ) ) . - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action ) ) . + Xml::fieldset( wfMsg( 'importinterwiki' ) ) . + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) . wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . Xml::hidden( 'action', 'submit' ) . Xml::hidden( 'source', 'interwiki' ) . @@ -200,6 +215,7 @@ class SpecialImport extends SpecialPage { $selected = ( $this->interwiki === $prefix ) ? ' selected="selected"' : ''; $wgOut->addHTML( Xml::option( $prefix, $prefix, $selected ) ); } + $wgOut->addHTML( Xml::closeElement( 'select' ) . Xml::input( 'frompage', 50, $this->frompage ) . @@ -213,7 +229,15 @@ class SpecialImport extends SpecialPage { "</td> </tr> <tr> - <td>" . + <td> + </td> + <td class='mw-input'>" . + Xml::checkLabel( wfMsg( 'import-interwiki-templates' ), 'interwikiTemplates', 'interwikiTemplates', $this->includeTemplates ) . + "</td> + </tr> + $importDepth + <tr> + <td class='mw-label'>" . Xml::label( wfMsg( 'import-interwiki-namespace' ), 'namespace' ) . "</td> <td class='mw-input'>" . @@ -232,7 +256,7 @@ class SpecialImport extends SpecialPage { <tr> <td> </td> - <td class='mw-input'>" . + <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'import-interwiki-submit' ), array( 'accesskey' => 's' ) ) . "</td> </tr>" . diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php index 8d573547..4ba1c811 100644 --- a/includes/specials/SpecialIpblocklist.php +++ b/includes/specials/SpecialIpblocklist.php @@ -162,7 +162,7 @@ class IPUnblockForm { * @return array array(message key, parameters) on failure, empty array on success */ - static function doUnblock(&$id, &$ip, &$reason, &$range = null) { + static function doUnblock(&$id, &$ip, &$reason, &$range = null, $blocker=null) { if ( $id ) { $block = Block::newFromID( $id ); if ( !$block ) { @@ -195,11 +195,22 @@ class IPUnblockForm { } // Yes, this is really necessary $id = $block->mId; + + # If the name was hidden and the blocking user cannot hide + # names, then don't allow any block removals... + if( $blocker && $block->mHideName && !$blocker->isAllowed('hideuser') ) { + return array('ipb_cant_unblock', htmlspecialchars($id)); + } # Delete block if ( !$block->delete() ) { return array('ipb_cant_unblock', htmlspecialchars($id)); } + + # Unset _deleted fields as needed + if( $block->mHideName ) { + IPBlockForm::unsuppressUserName( $block->mAddress, $block->mUser ); + } # Make log entry $log = new LogPage( 'block' ); @@ -208,8 +219,8 @@ class IPUnblockForm { } function doSubmit() { - global $wgOut; - $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range); + global $wgOut, $wgUser; + $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range, $wgUser); if(!empty($retval)) { $key = array_shift($retval); @@ -238,7 +249,7 @@ class IPUnblockForm { $conds = array(); $matches = array(); // Is user allowed to see all the blocks? - if ( !$wgUser->isAllowed( 'suppress' ) ) + if ( !$wgUser->isAllowed( 'hideuser' ) ) $conds['ipb_deleted'] = 0; if ( $this->ip == '' ) { // No extra conditions @@ -306,7 +317,7 @@ class IPUnblockForm { } function searchForm() { - global $wgTitle, $wgScript, $wgRequest; + global $wgTitle, $wgScript, $wgRequest, $wgLang; $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); $nondefaults = array(); @@ -330,7 +341,7 @@ class IPUnblockForm { $links[] = wfMsgHtml( 'ipblocklist-sh-userblocks', $ubLink ); $links[] = wfMsgHtml( 'ipblocklist-sh-tempblocks', $tbLink ); $links[] = wfMsgHtml( 'ipblocklist-sh-addressblocks', $sipbLink ); - $hl = implode( ' ' . wfMsg( 'pipe-separator' ) . ' ', $links ); + $hl = $wgLang->pipeList( $links ); return Xml::tags( 'form', array( 'action' => $wgScript ), @@ -418,7 +429,7 @@ class IPUnblockForm { $properties[] = $msg['blocklist-nousertalk']; } - $properties = implode( ', ', $properties ); + $properties = $wgLang->commaList( $properties ); $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); @@ -434,7 +445,7 @@ class IPUnblockForm { # Create changeblocklink for all blocks with exception of autoblocks if( !$block->mAuto ) { - $changeblocklink = ' ' . wfMsg( 'pipe-separator' ) . ' ' . + $changeblocklink = wfMsg( 'pipe-separator' ) . $sk->link( SpecialPage::getTitleFor( 'Blockip', $block->mAddress ), $msg['change-blocklink'], array(), array(), 'known' ); diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 6b9df58f..267ef690 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -15,7 +15,7 @@ function wfSpecialLinkSearch( $par ) { list( $limit, $offset ) = wfCheckLimits(); - global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode; + global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang; $target = $GLOBALS['wgRequest']->getVal( 'target', $par ); $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null ); @@ -48,7 +48,7 @@ function wfSpecialLinkSearch( $par ) { $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' ); - $wgOut->addWikiText( wfMsg( 'linksearch-text', '<nowiki>' . implode( ', ', $wgUrlProtocols) . '</nowiki>' ) ); + $wgOut->addWikiText( wfMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols) . '</nowiki>' ) ); $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . Xml::hidden( 'title', $self->getPrefixedDbKey() ) . '<fieldset>' . diff --git a/includes/specials/SpecialListUserRestrictions.php b/includes/specials/SpecialListUserRestrictions.php index 27b24298..98e7111f 100644 --- a/includes/specials/SpecialListUserRestrictions.php +++ b/includes/specials/SpecialListUserRestrictions.php @@ -24,9 +24,10 @@ function wfSpecialListUserRestrictions() { class SpecialListUserRestrictionsForm { public function getHTML() { global $wgRequest, $wgScript, $wgTitle; + $action = htmlspecialchars( $wgScript ); $s = ''; $s .= Xml::fieldset( wfMsg( 'listuserrestrictions-legend' ) ); - $s .= "<form action=\"{$wgScript}\">"; + $s .= "<form action=\"{$action}\">"; $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ); $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-type' ), 'type' ) . ' ' . self::typeSelector( 'type', $wgRequest->getVal( 'type' ), 'type' ); diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php index d2178ee0..e15b6959 100644 --- a/includes/specials/SpecialListfiles.php +++ b/includes/specials/SpecialListfiles.php @@ -58,7 +58,7 @@ class ImageListPager extends TablePager { 'img_description' => wfMsg( 'listfiles_description' ), ); if( !$wgMiserMode ) { - $this->mFieldNames['COUNT(oi_archive_name)'] = wfMsg( 'listfiles_count' ); + $this->mFieldNames['count'] = wfMsg( 'listfiles_count' ); } } return $this->mFieldNames; @@ -74,11 +74,25 @@ class ImageListPager extends TablePager { $fields = array_keys( $this->getFieldNames() ); $fields[] = 'img_user'; $options = $join_conds = array(); + # Depends on $wgMiserMode - if( isset($this->mFieldNames['COUNT(oi_archive_name)']) ) { + if( isset( $this->mFieldNames['count'] ) ) { $tables[] = 'oldimage'; - $options = array('GROUP BY' => 'img_name'); - $join_conds = array('oldimage' => array('LEFT JOIN','oi_name = img_name') ); + + # Need to rewrite this one + foreach ( $fields as &$field ) + if ( $field == 'count' ) + $field = 'COUNT(oi_archive_name) as count'; + unset( $field ); + + $dbr = wfGetDB( DB_SLAVE ); + if( $dbr->implicitGroupby() ) { + $options = array( 'GROUP BY' => 'img_name' ); + } else { + $columnlist = implode( ',', preg_grep( '/^img/', array_keys( $this->getFieldNames() ) ) ); + $options = array( 'GROUP BY' => "img_user, $columnlist" ); + } + $join_conds = array( 'oldimage' => array( 'LEFT JOIN', 'oi_name = img_name' ) ); } return array( 'tables' => $tables, @@ -136,7 +150,7 @@ class ImageListPager extends TablePager { return $this->getSkin()->formatSize( $value ); case 'img_description': return $this->getSkin()->commentBlock( $value ); - case 'COUNT(oi_archive_name)': + case 'count': return intval($value)+1; } } diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php index 5c76df8c..d1fc0818 100644 --- a/includes/specials/SpecialListgrouprights.php +++ b/includes/specials/SpecialListgrouprights.php @@ -63,10 +63,13 @@ class SpecialListGroupRights extends SpecialPage { $grouppage = $this->skin->makeLink( $grouppageLocalized, $groupnameLocalized ); } - if ( !in_array( $group, $wgImplicitGroups ) ) { + if ( $group === 'user' ) { + // Link to Special:listusers for implicit group 'user' + $grouplink = '<br />' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), '' ); + } elseif ( !in_array( $group, $wgImplicitGroups ) ) { $grouplink = '<br />' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), 'group=' . $group ); } else { - // No link to Special:listusers for implicit groups as they are unlistable + // No link to Special:listusers for other implicit groups as they are unlistable $grouplink = ''; } diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index 17bec70e..aa057801 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -53,6 +53,7 @@ class UsersPager extends AlphabeticPager { $this->requestedGroup = ''; } $this->editsOnly = $wgRequest->getBool( 'editsOnly' ); + $this->creationSort = $wgRequest->getBool( 'creationSort' ); $this->requestedUser = ''; if ( $un != '' ) { @@ -66,7 +67,7 @@ class UsersPager extends AlphabeticPager { function getIndexField() { - return 'user_name'; + return $this->creationSort ? 'user_id' : 'user_name'; } function getQueryInfo() { @@ -74,14 +75,19 @@ class UsersPager extends AlphabeticPager { $conds = array(); // Don't show hidden names $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0'; - if ($this->requestedGroup != "") { + if( $this->requestedGroup != '' ) { $conds['ug_group'] = $this->requestedGroup; $useIndex = ''; } else { - $useIndex = $dbr->useIndexClause('user_name'); + $useIndex = $dbr->useIndexClause( $this->creationSort ? 'PRIMARY' : 'user_name'); } - if ($this->requestedUser != "") { - $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); + if( $this->requestedUser != '' ) { + # Sorted either by account creation or name + if( $this->creationSort ) { + $conds[] = 'user_id >= ' . User::idFromName( $this->requestedUser ); + } else { + $conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser ); + } } if( $this->editsOnly ) { $conds[] = 'user_editcount > 0'; @@ -92,12 +98,14 @@ class UsersPager extends AlphabeticPager { $query = array( 'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ", - 'fields' => array('user_name', - 'MAX(user_id) AS user_id', + 'fields' => array( + $this->creationSort ? 'MAX(user_name) AS user_name' : 'user_name', + $this->creationSort ? 'user_id' : 'MAX(user_id) AS user_id', 'MAX(user_editcount) AS edits', 'COUNT(ug_group) AS numgroups', - 'MAX(ug_group) AS singlegroup'), - 'options' => array('GROUP BY' => 'user_name'), + 'MAX(ug_group) AS singlegroup', + 'MIN(user_registration) AS creation'), + 'options' => array('GROUP BY' => $this->creationSort ? 'user_id' : 'user_name'), 'conds' => $conds ); @@ -115,7 +123,7 @@ class UsersPager extends AlphabeticPager { $list = array(); foreach( self::getGroups( $row->user_id ) as $group ) $list[] = self::buildGroupLink( $group ); - $groups = implode( ', ', $list ); + $groups = $wgLang->commaList( $list ); } elseif( $row->numgroups == 1 ) { $groups = self::buildGroupLink( $row->singlegroup ); } else { @@ -131,8 +139,17 @@ class UsersPager extends AlphabeticPager { } else { $edits = ''; } + + $created = ''; + # Some rows may be NULL + if( $row->creation ) { + $d = $wgLang->date( wfTimestamp( TS_MW, $row->creation ), true ); + $t = $wgLang->time( wfTimestamp( TS_MW, $row->creation ), true ); + $created = ' (' . wfMsgHtml( 'usercreated', $d, $t ) . ')'; + } + wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) ); - return "<li>{$item}{$edits}</li>"; + return "<li>{$item}{$edits}{$created}</li>"; } function getBody() { @@ -154,10 +171,9 @@ class UsersPager extends AlphabeticPager { $self = $this->getTitle(); # Form tag - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . - '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'listusers' ) ); - $out .= Xml::hidden( 'title', $self->getPrefixedDbKey() ); + $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) . + Xml::fieldset( wfMsg( 'listusers' ) ) . + Xml::hidden( 'title', $self->getPrefixedDbKey() ); # Username field $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . @@ -172,25 +188,31 @@ class UsersPager extends AlphabeticPager { $out .= Xml::closeElement( 'select' ) . '<br/>'; $out .= Xml::checkLabel( wfMsg('listusers-editsonly'), 'editsOnly', 'editsOnly', $this->editsOnly ); $out .= ' '; + $out .= Xml::checkLabel( wfMsg('listusers-creationsort'), 'creationSort', 'creationSort', $this->creationSort ); + $out .= '<br/>'; wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); # Submit button and form bottom - if( $this->mLimit ) - $out .= Xml::hidden( 'limit', $this->mLimit ); + $out .= Xml::hidden( 'limit', $this->mLimit ); $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) ); - $out .= '</fieldset>' . + $out .= Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); return $out; } + /** + * Get a list of all explicit groups + * @return array + */ function getAllGroups() { $result = array(); foreach( User::getAllGroups() as $group ) { $result[$group] = User::getGroupName( $group ); } + asort( $result ); return $result; } diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index 492c2608..2382344b 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -45,6 +45,7 @@ function wfSpecialLog( $par = '' ) { $pattern = $wgRequest->getBool( 'pattern' ); $y = $wgRequest->getIntOrNull( 'year' ); $m = $wgRequest->getIntOrNull( 'month' ); + $tagFilter = $wgRequest->getVal( 'tagfilter' ); # Don't let the user get stuck with a certain date $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; if( $skip ) { @@ -53,12 +54,12 @@ function wfSpecialLog( $par = '' ) { } # Create a LogPager item to get the results and a LogEventsList item to format them... $loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 ); - $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m ); + $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m, $tagFilter ); # Set title and add header $loglist->showHeader( $pager->getType() ); # Show form options $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), - $pager->getYear(), $pager->getMonth(), $pager->getFilterParams() ); + $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $tagFilter ); # Insert list $logBody = $pager->getBody(); if( $logBody ) { diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index f870406c..c51ce7c3 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -97,7 +97,7 @@ class MergehistoryForm { ); } - if ( $this->mTargetObj->equals( $this->mDestObj ) ) { + if ( $this->mTargetObj && $this->mDestObj && $this->mTargetObj->equals( $this->mDestObj ) ) { $errors[] = wfMsgExt( 'mergehistory-same-destination', array( 'parse' ) ); } @@ -142,7 +142,7 @@ class MergehistoryForm { } private function showHistory() { - global $wgLang, $wgContLang, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; $this->sk = $wgUser->getSkin(); @@ -163,27 +163,22 @@ class MergehistoryForm { if( $haveRevisions ) { # Format the user-visible controls (comment field, submission button) # in a nice little table - $align = $wgContLang->isRtl() ? 'left' : 'right'; $table = Xml::openElement( 'fieldset' ) . - Xml::openElement( 'table' ) . + wfMsgExt( 'mergehistory-merge', array('parseinline'), + $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . + Xml::openElement( 'table', array( 'id' => 'mw-mergehistory-table' ) ) . "<tr> - <td colspan='2'>" . - wfMsgExt( 'mergehistory-merge', array('parseinline'), - $this->mTargetObj->getPrefixedText(), $this->mDestObj->getPrefixedText() ) . + <td class='mw-label'>" . + Xml::label( wfMsg( 'mergehistory-reason' ), 'wpComment' ) . "</td> - </tr> - <tr> - <td align='$align'>" . - Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) . - "</td> - <td>" . - Xml::input( 'wpComment', 50, $this->mComment ) . + <td class='mw-input'>" . + Xml::input( 'wpComment', 50, $this->mComment, array('id' => 'wpComment') ) . "</td> </tr> <tr> <td> </td> - <td>" . + <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . "</td> </tr>" . @@ -439,7 +434,7 @@ class MergeHistoryPager extends ReverseChronologicalPager { return array( 'tables' => array('revision','page'), 'fields' => array( 'rev_minor_edit', 'rev_timestamp', 'rev_user', 'rev_user_text', 'rev_comment', - 'rev_id', 'rev_page', 'rev_text_id', 'rev_len', 'rev_deleted' ), + 'rev_id', 'rev_page', 'rev_parent_id', 'rev_text_id', 'rev_len', 'rev_deleted' ), 'conds' => $conds ); } diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index acc27625..8fcf33a9 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -234,15 +234,22 @@ class MovePageForm { } if( ($this->oldTitle->hasSubpages() || $this->oldTitle->getTalkPage()->hasSubpages()) - && $this->oldTitle->userCan( 'move-subpages' ) ) { + && $this->oldTitle->userCan( 'move-subpages' ) ) + { + global $wgMaximumMovedPages, $wgLang; + $wgOut->addHTML( " <tr> <td></td> <td class=\"mw-input\">" . - Xml::checkLabel( wfMsg( - $this->oldTitle->hasSubpages() - ? 'move-subpages' - : 'move-talk-subpages' + Xml::checkLabel( wfMsgExt( + ( $this->oldTitle->hasSubpages() + ? 'move-subpages' + : 'move-talk-subpages' ), + array( 'parsemag' ), + $wgLang->formatNum( $wgMaximumMovedPages ), + # $2 to allow use of PLURAL in message. + $wgMaximumMovedPages ), 'wpMovesubpages', 'wpMovesubpages', # Don't check the box if we only have talk subpages to @@ -278,6 +285,7 @@ class MovePageForm { ); $this->showLogFragment( $this->oldTitle, $wgOut ); + $this->showSubpages( $this->oldTitle, $wgOut ); } @@ -375,6 +383,8 @@ class MovePageForm { # would mean that you couldn't move them back in one operation, which # is bad. FIXME: A specific error message should be given in this # case. + + // FIXME: Use Title::moveSubpages() here $dbr = wfGetDB( DB_MASTER ); if( $this->moveSubpages && ( MWNamespace::hasSubpages( $nt->getNamespace() ) || ( @@ -489,4 +499,32 @@ class MovePageForm { LogEventsList::showLogExtract( $out, 'move', $title->getPrefixedText() ); } + function showSubpages( $title, $out ) { + global $wgUser, $wgLang; + + if( !MWNamespace::hasSubpages( $title->getNamespace() ) ) + return; + + $subpages = $title->getSubpages(); + $count = $subpages instanceof TitleArray ? $subpages->count() : 0; + + $out->wrapWikiMsg( '== $1 ==', array( 'movesubpage', $count ) ); + + # No subpages. + if ( $count == 0 ) { + $out->addWikiMsg( 'movenosubpage' ); + return; + } + + $out->addWikiMsg( 'movesubpagetext', $wgLang->formatNum( $count ) ); + $skin = $wgUser->getSkin(); + $out->addHTML( "<ul>\n" ); + + foreach( $subpages as $subpage ) { + $link = $skin->link( $subpage ); + $out->addHTML( "<li>$link</li>\n" ); + } + $out->addHTML( "</ul>\n" ); + } } + diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php index 08e776d8..886c41a2 100644 --- a/includes/specials/SpecialNewpages.php +++ b/includes/specials/SpecialNewpages.php @@ -24,7 +24,7 @@ class SpecialNewpages extends SpecialPage { $opts = new FormOptions(); $this->opts = $opts; // bind $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', false ); + $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'newpageshidepatrolled' ) ); $opts->add( 'hidebots', false ); $opts->add( 'hideredirs', true ); $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); @@ -32,6 +32,7 @@ class SpecialNewpages extends SpecialPage { $opts->add( 'namespace', '0' ); $opts->add( 'username', '' ); $opts->add( 'feed', '' ); + $opts->add( 'tagfilter', '' ); // Set values $opts->fetchValuesFromRequest( $wgRequest ); @@ -121,7 +122,7 @@ class SpecialNewpages extends SpecialPage { } protected function filterLinks() { - global $wgGroupPermissions, $wgUser; + global $wgGroupPermissions, $wgUser, $wgLang; // show/hide links $showhide = array( wfMsgHtml( 'show' ), wfMsgHtml( 'hide' ) ); @@ -154,7 +155,7 @@ class SpecialNewpages extends SpecialPage { $links[$key] = wfMsgHtml( $msg, $link ); } - return implode( ' | ', $links ); + return $wgLang->pipeList( $links ); } protected function form() { @@ -176,6 +177,10 @@ class SpecialNewpages extends SpecialPage { } $hidden = implode( "\n", $hidden ); + $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); + if ($tagFilter) + list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; + $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . Xml::fieldset( wfMsg( 'newpages' ) ) . @@ -187,7 +192,15 @@ class SpecialNewpages extends SpecialPage { <td class='mw-input'>" . Xml::namespaceSelector( $namespace, 'all' ) . "</td> - </tr>" . + </tr>" . ( $tagFilter ? ( + "<tr> + <td class='mw-label'>" . + $tagFilterLabel . + "</td> + <td class='mw-input'>" . + $tagFilterSelector . + "</td> + </tr>" ) : '' ) . ($wgEnableNewpagesUserFilter ? "<tr> <td class='mw-label'>" . @@ -235,20 +248,32 @@ class SpecialNewpages extends SpecialPage { */ public function formatRow( $result ) { global $wgLang, $wgContLang, $wgUser; + + $classes = array(); + $dm = $wgContLang->getDirMark(); $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title ); $time = $wgLang->timeAndDate( $result->rc_timestamp, true ); - $plink = $this->skin->makeKnownLinkObj( $title, '', $this->patrollable( $result ) ? 'rcid=' . $result->rc_id : '' ); + $query = $this->patrollable( $result ) ? "rcid={$result->rc_id}&redirect=no" : 'redirect=no'; + $plink = $this->skin->makeKnownLinkObj( $title, '', $query ); $hist = $this->skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' ); $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->length ) ); $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); $comment = $this->skin->commentBlock( $result->rc_comment ); - $css = $this->patrollable( $result ) ? " class='not-patrolled'" : ''; + + if ( $this->patrollable( $result ) ) + $classes[] = 'not-patrolled'; + + # Tags, if any. + list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); + $classes = array_merge( $classes, $newClasses ); + + $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : ''; - return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment}</li>\n"; + return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n"; } /** @@ -331,7 +356,7 @@ class SpecialNewpages extends SpecialPage { protected function feedItemDesc( $row ) { $revision = Revision::newFromId( $row->rev_id ); if( $revision ) { - return '<p>' . htmlspecialchars( $revision->getUserText() ) . ': ' . + return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; @@ -377,7 +402,6 @@ class NewPagesPager extends ReverseChronologicalPager { } else { $rcIndexes = array( 'rc_timestamp' ); } - $conds[] = 'page_id = rc_cur_id'; # $wgEnableNewpagesUserFilter - temp WMF hack if( $wgEnableNewpagesUserFilter && $user ) { @@ -399,13 +423,29 @@ class NewPagesPager extends ReverseChronologicalPager { $conds['page_is_redirect'] = 0; } - return array( + $info = array( 'tables' => array( 'recentchanges', 'page' ), 'fields' => 'rc_namespace,rc_title, rc_cur_id, rc_user,rc_user_text,rc_comment, - rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id', + rc_timestamp,rc_patrolled,rc_id,page_len as length, page_latest as rev_id, ts_tags', 'conds' => $conds, - 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ) + 'options' => array( 'USE INDEX' => array('recentchanges' => $rcIndexes) ), + 'join_conds' => array( + 'page' => array('INNER JOIN', 'page_id=rc_cur_id'), + ), ); + + ## Empty array for fields, it'll be set by us anyway. + $fields = array(); + + ## Modify query for tags + ChangeTags::modifyDisplayQuery( $info['tables'], + $fields, + $info['conds'], + $info['join_conds'], + $info['options'], + $this->opts['tagfilter'] ); + + return $info; } function getIndexField() { diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index ca2236ee..f4a42ef4 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -26,13 +26,13 @@ class PreferencesForm { var $mUserLanguage, $mUserVariant; var $mSearch, $mRecent, $mRecentDays, $mTimeZone, $mHourDiff, $mSearchLines, $mSearchChars, $mAction; var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize; - var $mUnderline, $mWatchlistEdits; + var $mUnderline, $mWatchlistEdits, $mGender; /** * Constructor * Load some values */ - function PreferencesForm( &$request ) { + function __construct( &$request ) { global $wgContLang, $wgUser, $wgAllowRealName; $this->mQuickbar = $request->getVal( 'wpQuickbar' ); @@ -60,11 +60,13 @@ class PreferencesForm { $this->mUnderline = $request->getInt( 'wpOpunderline' ); $this->mAction = $request->getVal( 'action' ); $this->mReset = $request->getCheck( 'wpReset' ); + $this->mRestoreprefs = $request->getCheck( 'wpRestore' ); $this->mPosted = $request->wasPosted(); $this->mSuccess = $request->getCheck( 'success' ); $this->mWatchlistDays = $request->getVal( 'wpWatchlistDays' ); $this->mWatchlistEdits = $request->getVal( 'wpWatchlistEdits' ); $this->mDisableMWSuggest = $request->getCheck( 'wpDisableMWSuggest' ); + $this->mGender = $request->getVal( 'wpGender' ); $this->mSaveprefs = $request->getCheck( 'wpSaveprefs' ) && $this->mPosted && @@ -117,6 +119,8 @@ class PreferencesForm { $this->mainPrefsForm( 'reset', wfMsg( 'prefsreset' ) ); } else if ( $this->mSaveprefs ) { $this->savePreferences(); + } else if ( $this->mRestoreprefs ) { + $this->restorePreferences(); } else { $this->resetPrefs(); $this->mainPrefsForm( '' ); @@ -204,6 +208,15 @@ class PreferencesForm { } } + function validateGender( $val ) { + $valid = array( 'male', 'female', 'unknown' ); + if ( in_array($val, $valid) ) { + return $val; + } else { + return User::getDefaultOption( 'gender' ); + } + } + /** * @access private */ @@ -269,6 +282,7 @@ class PreferencesForm { $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) ); $wgUser->setOption( 'watchlistdays', $this->validateFloat( $this->mWatchlistDays, 0, 7 ) ); $wgUser->setOption( 'disablesuggest', $this->mDisableMWSuggest ); + $wgUser->setOption( 'gender', $this->validateGender( $this->mGender ) ); # Set search namespace options foreach( $this->mSearchNs as $i => $value ) { @@ -420,6 +434,7 @@ class PreferencesForm { $this->mUnderline = $wgUser->getOption( 'underline' ); $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' ); $this->mDisableMWSuggest = $wgUser->getBoolOption( 'disablesuggest' ); + $this->mGender = $wgUser->getOption( 'gender' ); $togs = User::getToggles(); foreach ( $togs as $tname ) { @@ -435,6 +450,18 @@ class PreferencesForm { wfRunHooks( 'ResetPreferences', array( $this, $wgUser ) ); } + + /** + * @access private + */ + function restorePreferences() { + global $wgUser, $wgOut; + $wgUser->restoreOptions(); + $wgUser->setCookies(); + $wgUser->saveSettings(); + $title = SpecialPage::getTitleFor( 'Preferences' ); + $wgOut->redirect( $title->getFullURL( 'success' ) ); + } /** * @access private @@ -647,12 +674,12 @@ class PreferencesForm { $userInformationHtml = $this->tableRow( wfMsgHtml( 'username' ), htmlspecialchars( $wgUser->getName() ) ) . - $this->tableRow( wfMsgHtml( 'uid' ), $wgLang->formatNum( htmlspecialchars( $wgUser->getId() ) ) ). + $this->tableRow( wfMsgHtml( 'uid' ), htmlspecialchars( $wgUser->getId() ) ) . $this->tableRow( wfMsgExt( 'prefs-memberingroups', array( 'parseinline' ), count( $userEffectiveGroupsArray ) ), $wgLang->commaList( $userEffectiveGroupsArray ) . - '<br />(' . implode( ' | ', $toolLinks ) . ')' + '<br />(' . $wgLang->pipeList( $toolLinks ) . ')' ) . $this->tableRow( @@ -720,7 +747,22 @@ class PreferencesForm { $this->tableRow( ' ', $this->getToggle( 'fancysig' ) ) ); - list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage ); + $gender = new XMLSelect( 'wpGender', 'wpGender', $this->mGender ); + $gender->addOption( wfMsg( 'gender-unknown' ), 'unknown' ); + $gender->addOption( wfMsg( 'gender-male' ), 'male' ); + $gender->addOption( wfMsg( 'gender-female' ), 'female' ); + + $wgOut->addHTML( + $this->tableRow( + Xml::label( wfMsg( 'yourgender' ), 'wpGender' ), + $gender->getHTML(), + Xml::tags( 'div', array( 'class' => 'prefsectiontip' ), + wfMsgExt( 'prefs-help-gender', 'parseinline' ) + ) + ) + ); + + list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage, false ); $wgOut->addHTML( $this->tableRow( $lsLabel, $lsSelect ) ); @@ -849,13 +891,26 @@ class PreferencesForm { } } asort($validSkinNames); - foreach ($validSkinNames as $skinkey => $sn ) { + foreach( $validSkinNames as $skinkey => $sn ) { $checked = $skinkey == $this->mSkin ? ' checked="checked"' : ''; $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) ); $previewlink = "(<a target='_blank' href=\"$mplink\">$previewtext</a>)"; + $extraLinks = ''; + global $wgAllowUserCss, $wgAllowUserJs; + if( $wgAllowUserCss ) { + $cssPage = Title::makeTitleSafe( NS_USER, $wgUser->getName().'/'.$skinkey.'.css' ); + $customCSS = $sk->makeLinkObj( $cssPage, wfMsgExt('prefs-custom-css', array() ) ); + $extraLinks .= " ($customCSS)"; + } + if( $wgAllowUserJs ) { + $jsPage = Title::makeTitleSafe( NS_USER, $wgUser->getName().'/'.$skinkey.'.js' ); + $customJS = $sk->makeLinkObj( $jsPage, wfMsgHtml('prefs-custom-js') ); + $extraLinks .= " ($customJS)"; + } if( $skinkey == $wgDefaultSkin ) $sn .= ' (' . wfMsg( 'default' ) . ')'; - $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked /> <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink<br />\n" ); + $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked /> + <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink{$extraLinks}<br />\n" ); } $wgOut->addHTML( "</fieldset>\n\n" ); } @@ -972,26 +1027,54 @@ class PreferencesForm { 'onchange' => 'javascript:updateTimezoneSelection(false)' ) ); $opt .= Xml::option( wfMsg( 'timezoneuseserverdefault' ), "System|$wgLocalTZoffset", $this->mTimeZone === "System|$wgLocalTZoffset" ); $opt .= Xml::option( wfMsg( 'timezoneuseoffset' ), 'Offset', $this->mTimeZone === 'Offset' ); + if ( function_exists( 'timezone_identifiers_list' ) ) { - $optgroup = ''; + # Read timezone list $tzs = timezone_identifiers_list(); sort( $tzs ); - $selZone = explode( '|', $this->mTimeZone, 3); + + # Precache localized region names + $tzRegions = array(); + $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' ); + $tzRegions['America'] = wfMsg( 'timezoneregion-america' ); + $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' ); + $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' ); + $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' ); + $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' ); + $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' ); + $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' ); + $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' ); + $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' ); + asort( $tzRegions ); + + $selZone = explode( '|', $this->mTimeZone, 3 ); $selZone = ( $selZone[0] == 'ZoneInfo' ) ? $selZone[2] : null; $now = date_create( 'now' ); + $optgroup = ''; + foreach ( $tzs as $tz ) { $z = explode( '/', $tz, 2 ); + # timezone_identifiers_list() returns a number of # backwards-compatibility entries. This filters them out of the # list presented to the user. - if ( count( $z ) != 2 || !in_array( $z[0], array( 'Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific' ) ) ) continue; + if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) ) + continue; + + # Localize region + $z[0] = $tzRegions[$z[0]]; + + # Create region groups if ( $optgroup != $z[0] ) { - if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); + if ( $optgroup !== '' ) { + $opt .= Xml::closeElement( 'optgroup' ); + } $optgroup = $z[0]; - $opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) ); + $opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) ) . "\n"; } + $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 ); - $opt .= Xml::option( str_replace( '_', ' ', $tz ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) ); + $opt .= Xml::option( str_replace( '_', ' ', $z[0] . '/' . $z[1] ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) ) . "\n"; } if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' ); } @@ -1052,7 +1135,7 @@ class PreferencesForm { $wgOut->addHTML( Xml::closeElement( 'fieldset' ) ); # Recent changes - global $wgRCMaxAge; + global $wgRCMaxAge, $wgUseRCPatrol; $wgOut->addHTML( Xml::fieldset( wfMsg( 'prefs-rc' ) ) . Xml::openElement( 'table' ) . @@ -1078,8 +1161,11 @@ class PreferencesForm { ); $toggles[] = 'hideminor'; - if( $wgRCShowWatchingUsers ) - $toggles[] = 'shownumberswatching'; + if( $wgUseRCPatrol ) { + $toggles[] = 'hidepatrolled'; + $toggles[] = 'newpageshidepatrolled'; + } + if( $wgRCShowWatchingUsers ) $toggles[] = 'shownumberswatching'; $toggles[] = 'usenewrc'; $wgOut->addHTML( @@ -1088,6 +1174,10 @@ class PreferencesForm { ); # Watchlist + $watchlistToggles = array( 'watchlisthideminor', 'watchlisthidebots', 'watchlisthideown', + 'watchlisthideanons', 'watchlisthideliu' ); + if( $wgUseRCPatrol ) $watchlistToggles[] = 'watchlisthidepatrolled'; + $wgOut->addHTML( Xml::fieldset( wfMsg( 'prefs-watchlist' ) ) . Xml::inputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) . ' ' . @@ -1097,7 +1187,7 @@ class PreferencesForm { Xml::inputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) . ' ' . wfMsgHTML( 'prefs-watchlist-edits-max' ) . '<br /><br />' . - $this->getToggles( array( 'watchlisthideminor', 'watchlisthidebots', 'watchlisthideown', 'watchlisthideanons', 'watchlisthideliu' ) ) + $this->getToggles( $watchlistToggles ) ); if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) ) { @@ -1156,25 +1246,34 @@ class PreferencesForm { # Misc # - $wgOut->addHTML('<fieldset><legend>' . wfMsg('prefs-misc') . '</legend>'); - $wgOut->addHTML( '<label for="wpStubs">' . wfMsg( 'stub-threshold' ) . '</label> ' ); - $wgOut->addHTML( Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) ); - $msgUnderline = htmlspecialchars( wfMsg ( 'tog-underline' ) ); - $msgUnderlinenever = htmlspecialchars( wfMsg ( 'underline-never' ) ); - $msgUnderlinealways = htmlspecialchars( wfMsg ( 'underline-always' ) ); - $msgUnderlinedefault = htmlspecialchars( wfMsg ( 'underline-default' ) ); - $uopt = $wgUser->getOption("underline"); - $s0 = $uopt == 0 ? ' selected="selected"' : ''; - $s1 = $uopt == 1 ? ' selected="selected"' : ''; - $s2 = $uopt == 2 ? ' selected="selected"' : ''; - $wgOut->addHTML(" -<div class='toggle'><p><label for='wpOpunderline'>$msgUnderline</label> -<select name='wpOpunderline' id='wpOpunderline'> -<option value=\"0\"$s0>$msgUnderlinenever</option> -<option value=\"1\"$s1>$msgUnderlinealways</option> -<option value=\"2\"$s2>$msgUnderlinedefault</option> -</select></p></div>"); + $uopt = $wgUser->getOption( 'underline' ); + $wgOut->addHTML( + Xml::fieldset( wfMsg( 'prefs-misc' ) ) . + Xml::openElement( 'table' ) . + '<tr> + <td class="mw-label">' . + // Xml::label() cannot be used because 'stub-threshold' contains plain HTML + Xml::tags( 'label', array( 'for' => 'wpStubs' ), wfMsg( 'stub-threshold' ) ) . + '</td> + <td class="mw-input">' . + Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) . + '</td> + </tr><tr> + <td class="mw-label">' . + Xml::label( wfMsg( 'tog-underline' ), 'wpOpunderline' ) . + '</td> + <td class="mw-input">' . + Xml::openElement( 'select', array( 'id' => 'wpOpunderline', 'name' => 'wpOpunderline' ) ) . + Xml::option( wfMsg ( 'underline-never' ), '0', $uopt == 0 ) . + Xml::option( wfMsg ( 'underline-always' ), '1', $uopt == 1 ) . + Xml::option( wfMsg ( 'underline-default' ), '2', $uopt == 2 ) . + Xml::closeElement( 'select' ) . + '</td> + </tr>' . + Xml::closeElement( 'table' ) + ); + # And now the rest = Misc. foreach ( $togs as $tname ) { if( !array_key_exists( $tname, $this->mUsedToggles ) ) { if( $tname == 'norollbackdiff' && $wgUser->isAllowed( 'rollback' ) ) @@ -1190,14 +1289,14 @@ class PreferencesForm { $token = htmlspecialchars( $wgUser->editToken() ); $skin = $wgUser->getSkin(); + $rtl = $wgContLang->isRTL() ? 'left' : 'right'; $wgOut->addHTML( " - <div id='prefsubmit'> - <div> - <input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) . '"'.$skin->tooltipAndAccesskey('save')." /> - <input type='submit' name='wpReset' value=\"" . wfMsgHtml( 'resetprefs' ) . "\" /> - </div> - - </div> + <table id='prefsubmit' cellpadding='0' width='100%' style='background:none;'><tr> + <td><input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) . + '"'.$skin->tooltipAndAccesskey('save')." /> + <input type='submit' name='wpReset' value=\"" . wfMsgHtml( 'resetprefs' ) . "\" /></td> + <td align='$rtl'><input type='submit' name='wpRestore' value=\"" . wfMsgHtml( 'restoreprefs' ) . "\" /></td> + </tr></table> <input type='hidden' name='wpEditToken' value=\"{$token}\" /> </div></form>\n" ); diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index ea0c1135..680fe343 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -6,9 +6,6 @@ */ class SpecialPrefixindex extends SpecialAllpages { // Inherit $maxPerPage - - // Define other properties - protected $nsfromMsg = 'allpagesprefix'; function __construct(){ parent::__construct( 'Prefixindex' ); @@ -26,7 +23,7 @@ class SpecialPrefixindex extends SpecialAllpages { # GET values $from = $wgRequest->getVal( 'from' ); - $prefix = $wgRequest->getVal( 'prefix' ); + $prefix = $wgRequest->getVal( 'prefix', '' ); $namespace = $wgRequest->getInt( 'namespace' ); $namespaces = $wgContLang->getNamespaces(); @@ -63,7 +60,7 @@ class SpecialPrefixindex extends SpecialAllpages { $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); $out .= "<tr> <td class='mw-label'>" . - Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) . + Xml::label( wfMsg( 'allpagesprefix' ), 'nsfrom' ) . "</td> <td class='mw-input'>" . Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) . @@ -90,7 +87,7 @@ class SpecialPrefixindex extends SpecialAllpages { * @param string $from list all pages from this name (default FALSE) */ function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) { - global $wgOut, $wgUser, $wgContLang; + global $wgOut, $wgUser, $wgContLang, $wgLang; $sk = $wgUser->getSkin(); @@ -99,7 +96,6 @@ class SpecialPrefixindex extends SpecialAllpages { $fromList = $this->getNamespaceKeyAndText($namespace, $from); $prefixList = $this->getNamespaceKeyAndText($namespace, $prefix); $namespaces = $wgContLang->getNamespaces(); - $align = $wgContLang->isRtl() ? 'left' : 'right'; if ( !$prefixList || !$fromList ) { $out = wfMsgWikiHtml( 'allpagesbadtitle' ); @@ -134,7 +130,7 @@ class SpecialPrefixindex extends SpecialAllpages { $n = 0; if( $res->numRows() > 0 ) { - $out = '<table style="background: inherit;" border="0" width="100%">'; + $out = Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-prefixindex-list-table' ) ); while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $t = Title::makeTitle( $s->page_namespace, $s->page_title ); @@ -157,7 +153,7 @@ class SpecialPrefixindex extends SpecialAllpages { if( ($n % 3) != 0 ) { $out .= '</tr>'; } - $out .= '</table>'; + $out .= Xml::closeElement( 'table' ); } else { $out = ''; } @@ -168,20 +164,27 @@ class SpecialPrefixindex extends SpecialAllpages { } else { $nsForm = $this->namespacePrefixForm( $namespace, $prefix ); $self = $this->getTitle(); - $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">'; - $out2 .= '<tr valign="top"><td>' . $nsForm; - $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' . - $sk->makeKnownLinkObj( $self, - wfMsg ( 'allpages' ) ); + $out2 = Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-prefixindex-nav-table' ) ) . + '<tr> + <td>' . + $nsForm . + '</td> + <td id="mw-prefixindex-nav-form">' . + $sk->makeKnownLinkObj( $self, wfMsg ( 'allpages' ) ); + if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { $namespaceparam = $namespace ? "&namespace=$namespace" : ""; - $out2 .= " | " . $sk->makeKnownLinkObj( - $self, - wfMsgHtml( 'nextpage', htmlspecialchars( $s->page_title ) ), - "from=" . wfUrlEncode( $s->page_title ) . - "&prefix=" . wfUrlEncode( $prefix ) . $namespaceparam ); + $out2 = $wgLang->pipeList( array( + $out2, + $sk->makeKnownLinkObj( + $self, + wfMsgHtml( 'nextpage', str_replace( '_',' ', htmlspecialchars( $s->page_title ) ) ), + "from=" . wfUrlEncode( $s->page_title ) . + "&prefix=" . wfUrlEncode( $prefix ) . $namespaceparam ) + ) ); } - $out2 .= "</td></tr></table><hr />"; + $out2 .= "</td></tr>" . + Xml::closeElement( 'table' ); } $wgOut->addHTML( $out2 . $out ); diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php index 4e56ca42..a38a8cd1 100644 --- a/includes/specials/SpecialProtectedpages.php +++ b/includes/specials/SpecialProtectedpages.php @@ -16,12 +16,12 @@ class ProtectedPagesForm { public function showList( $msg = '' ) { global $wgOut, $wgRequest; - if ( "" != $msg ) { + if( "" != $msg ) { $wgOut->setSubtitle( $msg ); } // Purge expired entries on one in every 10 queries - if ( !mt_rand( 0, 10 ) ) { + if( !mt_rand( 0, 10 ) ) { Title::purgeExpiredRestrictions(); } @@ -37,7 +37,7 @@ class ProtectedPagesForm { $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size, $indefOnly, $cascadeOnly ) ); - if ( $pager->getNumRows() ) { + if( $pager->getNumRows() ) { $s = $pager->getNavigationBar(); $s .= "<ul>" . $pager->getBody() . @@ -73,31 +73,34 @@ class ProtectedPagesForm { $description_items[] = $protType; - if ( $row->pr_cascade ) { + if( $row->pr_cascade ) { $description_items[] = wfMsg( 'protect-summary-cascade' ); } $expiry_description = ''; $stxt = ''; - if ( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { + if( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { $expiry = Block::decodeExpiry( $row->pr_expiry ); - $expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) , $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); + $expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) , + $wgLang->date( $expiry ) , $wgLang->time( $expiry ) ); $description_items[] = $expiry_description; } - if (!is_null($size = $row->page_len)) { + if(!is_null($size = $row->page_len)) { $stxt = $wgContLang->getDirMark() . ' ' . $skin->formatRevisionSize( $size ); } # Show a link to the change protection form for allowed users otherwise a link to the protection log if( $wgUser->isAllowed( 'protect' ) ) { - $changeProtection = ' (' . $skin->makeKnownLinkObj( $title, wfMsgHtml( 'protect_change' ), 'action=unprotect' ) . ')'; + $changeProtection = ' (' . $skin->makeKnownLinkObj( $title, wfMsgHtml( 'protect_change' ), + 'action=unprotect' ) . ')'; } else { $ltitle = SpecialPage::getTitleFor( 'Log' ); - $changeProtection = ' (' . $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'protectlogpage' ), 'type=protect&page=' . $title->getPrefixedUrl() ) . ')'; + $changeProtection = ' (' . $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'protectlogpage' ), + 'type=protect&page=' . $title->getPrefixedUrl() ) . ')'; } wfProfileOut( __METHOD__ ); @@ -220,7 +223,7 @@ class ProtectedPagesForm { // First pass to load the log names foreach( $wgRestrictionLevels as $type ) { - if ( $type !='' && $type !='*') { + if( $type !='' && $type !='*') { $text = wfMsg("restriction-level-$type"); $m[$text] = $type; } @@ -248,8 +251,9 @@ class ProtectedPagesPager extends AlphabeticPager { public $mForm, $mConds; private $type, $level, $namespace, $sizetype, $size, $indefonly; - function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', - $size=0, $indefonly = false, $cascadeonly = false ) { + function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0, + $indefonly = false, $cascadeonly = false ) + { $this->mForm = $form; $this->mConds = $conds; $this->type = ( $type ) ? $type : 'edit'; @@ -263,15 +267,12 @@ class ProtectedPagesPager extends AlphabeticPager { } function getStartBody() { - wfProfileIn( __METHOD__ ); # Do a link batch query $lb = new LinkBatch; while( $row = $this->mResult->fetchObject() ) { $lb->add( $row->page_namespace, $row->page_title ); } $lb->execute(); - - wfProfileOut( __METHOD__ ); return ''; } @@ -281,10 +282,11 @@ class ProtectedPagesPager extends AlphabeticPager { function getQueryInfo() { $conds = $this->mConds; - $conds[] = 'pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ); + $conds[] = '(pr_expiry>' . $this->mDb->addQuotes( $this->mDb->timestamp() ) . + 'OR pr_expiry IS NULL)'; $conds[] = 'page_id=pr_page'; $conds[] = 'pr_type=' . $this->mDb->addQuotes( $this->type ); - + if( $this->sizetype=='min' ) { $conds[] = 'page_len>=' . $this->size; } else if( $this->sizetype=='max' ) { @@ -294,7 +296,7 @@ class ProtectedPagesPager extends AlphabeticPager { if( $this->indefonly ) { $conds[] = "pr_expiry = 'infinity' OR pr_expiry IS NULL"; } - if ( $this->cascadeonly ) { + if( $this->cascadeonly ) { $conds[] = "pr_cascade = '1'"; } @@ -318,8 +320,6 @@ class ProtectedPagesPager extends AlphabeticPager { * Constructor */ function wfSpecialProtectedpages() { - $ppForm = new ProtectedPagesForm(); - $ppForm->showList(); } diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php index f4bff30b..31199b23 100644 --- a/includes/specials/SpecialRandompage.php +++ b/includes/specials/SpecialRandompage.php @@ -23,7 +23,7 @@ class RandomPage extends SpecialPage { } public function setNamespace ( $ns ) { - if( $ns < NS_MAIN ) $ns = NS_MAIN; + if( !$ns || $ns < NS_MAIN ) $ns = NS_MAIN; $this->namespaces = array( $ns ); } diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index 8c14e1fc..91c0ecbe 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -23,11 +23,11 @@ class SpecialRecentChanges extends SpecialPage { $opts->add( 'limit', (int)$wgUser->getOption( 'rclimit' ) ); $opts->add( 'from', '' ); - $opts->add( 'hideminor', (bool)$wgUser->getOption( 'hideminor' ) ); + $opts->add( 'hideminor', $wgUser->getBoolOption( 'hideminor' ) ); $opts->add( 'hidebots', true ); $opts->add( 'hideanons', false ); $opts->add( 'hideliu', false ); - $opts->add( 'hidepatrolled', false ); + $opts->add( 'hidepatrolled', $wgUser->getBoolOption( 'hidepatrolled' ) ); $opts->add( 'hidemyself', false ); $opts->add( 'namespace', '', FormOptions::INTNULL ); @@ -35,6 +35,7 @@ class SpecialRecentChanges extends SpecialPage { $opts->add( 'categories', '' ); $opts->add( 'categories_any', false ); + $opts->add( 'tagfilter', '' ); return $opts; } @@ -54,7 +55,7 @@ class SpecialRecentChanges extends SpecialPage { $this->parseParameters( $parameters, $opts ); } - $opts->validateIntBounds( 'limit', 0, 5000 ); + $opts->validateIntBounds( 'limit', 0, 500 ); return $opts; } @@ -66,7 +67,8 @@ class SpecialRecentChanges extends SpecialPage { public function feedSetup() { global $wgFeedLimit, $wgRequest; $opts = $this->getDefaultOptions(); - $opts->fetchValuesFromRequest( $wgRequest, array( 'days', 'limit', 'hideminor' ) ); + # Feed is cached on limit,hideminor; other params would randomly not work + $opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor' ) ); $opts->validateIntBounds( 'limit', 0, $wgFeedLimit ); return $opts; } @@ -108,13 +110,14 @@ class SpecialRecentChanges extends SpecialPage { foreach( $rows as $row ) { $batch->add( NS_USER, $row->rc_user_text ); $batch->add( NS_USER_TALK, $row->rc_user_text ); + $batch->add( $row->rc_namespace, $row->rc_title ); } $batch->execute(); } - + $target = isset($opts['target']) ? $opts['target'] : ''; // RCL has targets if( $feedFormat ) { list( $feed, $feedObj ) = $this->getFeedObject( $feedFormat ); - $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod ); + $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod, $target ); } else { $this->webOutput( $rows, $opts ); } @@ -267,6 +270,7 @@ class SpecialRecentChanges extends SpecialPage { $tables = array( 'recentchanges' ); $join_conds = array(); + $query_options = array( 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ); $uid = $wgUser->getId(); $dbr = wfGetDB( DB_SLAVE ); @@ -274,21 +278,38 @@ class SpecialRecentChanges extends SpecialPage { $namespace = $opts['namespace']; $invert = $opts['invert']; + $join_conds = array(); + // JOIN on watchlist for users if( $uid ) { $tables[] = 'watchlist'; - $join_conds = array( 'watchlist' => array('LEFT JOIN', - "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace") ); + $join_conds['watchlist'] = array('LEFT JOIN', + "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace"); } + if ($wgUser->isAllowed("rollback")) { + $tables[] = 'page'; + $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); + } + // Tag stuff. + $fields = array(); + // Fields are * in this case, so let the function modify an empty array to keep it happy. + ChangeTags::modifyDisplayQuery( $tables, + $fields, + $conds, + $join_conds, + $query_options, + $opts['tagfilter'] + ); wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); // Is there either one namespace selected or excluded? + // Tag filtering also has a better index. // Also, if this is "all" or main namespace, just use timestamp index. - if( is_null($namespace) || $invert || $namespace == NS_MAIN ) { + if( is_null($namespace) || $invert || $namespace == NS_MAIN || $opts['tagfilter'] ) { $res = $dbr->select( $tables, '*', $conds, __METHOD__, - array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, - 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ), + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + + $query_options, $join_conds ); // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! } else { @@ -372,7 +393,7 @@ class SpecialRecentChanges extends SpecialPage { } $rc->numberofWatchingusers = $watcherCache[$obj->rc_namespace][$obj->rc_title]; } - $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ) ); + $s .= $list->recentChangesLine( $rc, !empty( $obj->wl_user ), $counter ); --$limit; } $s .= $list->endRecentChangesList(); @@ -453,6 +474,10 @@ class SpecialRecentChanges extends SpecialPage { $extraOpts['category'] = $this->categoryFilterForm( $opts ); } + $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); + if ( count($tagFilter) ) + $extraOpts['tagfilter'] = $tagFilter; + wfRunHooks( 'SpecialRecentChangesPanel', array( &$extraOpts, $opts ) ); return $extraOpts; } @@ -587,14 +612,14 @@ class SpecialRecentChanges extends SpecialPage { $options = $nondefaults + $defaults; $note = ''; + if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) { + $note .= '<div class="mw-rclegend">' . wfMsgExt( 'rclegend', array('parseinline') ) . "</div>\n"; + } if( $options['from'] ) { $note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ), $wgLang->formatNum( $options['limit'] ), $wgLang->timeanddate( $options['from'], true ) ) . '<br />'; } - if( !wfEmptyMsg( 'rclegend', wfMsg('rclegend') ) ) { - $note .= wfMsgExt( 'rclegend', array('parseinline') ) . '<br />'; - } # Sort data for display and make sure it's unique after we've added user data. $wgRCLinkLimits[] = $options['limit']; @@ -609,14 +634,14 @@ class SpecialRecentChanges extends SpecialPage { $cl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), array( 'limit' => $value ), $nondefaults, $value == $options['limit'] ) ; } - $cl = implode( ' | ', $cl ); + $cl = $wgLang->pipeList( $cl ); // day links, reset 'from' to none foreach( $wgRCLinkDays as $value ) { $dl[] = $this->makeOptionsLink( $wgLang->formatNum( $value ), array( 'days' => $value, 'from' => '' ), $nondefaults, $value == $options['days'] ) ; } - $dl = implode( ' | ', $dl ); + $dl = $wgLang->pipeList( $dl ); // show/hide links @@ -641,7 +666,7 @@ class SpecialRecentChanges extends SpecialPage { if( $wgUser->useRCPatrol() ) $links[] = wfMsgHtml( 'rcshowhidepatr', $patrLink ); $links[] = wfMsgHtml( 'rcshowhidemine', $myselfLink ); - $hl = implode( ' | ', $links ); + $hl = $wgLang->pipeList( $links ); // show from this onward link $now = $wgLang->timeanddate( wfTimestampNow(), true ); diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php index c0734354..c58ffff0 100644 --- a/includes/specials/SpecialRecentchangeslinked.php +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -15,6 +15,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $opts = parent::getDefaultOptions(); $opts->add( 'target', '' ); $opts->add( 'showlinkedto', false ); + $opts->add( 'tagfilter', '' ); return $opts; } @@ -22,9 +23,10 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $opts['target'] = $par; } - public function feedSetup(){ + public function feedSetup() { global $wgRequest; $opts = parent::feedSetup(); + # Feed is cached on limit,hideminor,target; other params would randomly not work $opts['target'] = $wgRequest->getVal( 'target' ); return $opts; } @@ -74,6 +76,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $tables = array( 'recentchanges' ); $select = array( $dbr->tableName( 'recentchanges' ) . '.*' ); $join_conds = array(); + $query_options = array(); // left join with watchlist table to highlight watched rows if( $uid = $wgUser->getId() ) { @@ -82,6 +85,9 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" ); } + ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds, + $query_options, $opts['tagfilter'] ); + // XXX: parent class does this, should we too? // wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); @@ -133,9 +139,14 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { } } - $subsql[] = $dbr->selectSQLText( array_merge( $tables, array( $link_table ) ), $select, $conds + $subconds, - __METHOD__, array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ), - $join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) ) ); + $subsql[] = $dbr->selectSQLText( + array_merge( $tables, array( $link_table ) ), + $select, + $conds + $subconds, + __METHOD__, + array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options, + $join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) ) + ); } if( count($subsql) == 0 ) @@ -163,6 +174,9 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) . Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' . Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) ); + $tagFilter = ChangeTags::buildTagFilterSelector( $opts['tagfilter'] ); + if ($tagFilter) + $extraOpts['tagfilter'] = $tagFilter; return $extraOpts; } diff --git a/includes/specials/SpecialRestrictUser.php b/includes/specials/SpecialRestrictUser.php index 761e0cd6..b946cde8 100644 --- a/includes/specials/SpecialRestrictUser.php +++ b/includes/specials/SpecialRestrictUser.php @@ -37,7 +37,8 @@ function wfSpecialRestrictUser( $par = null ) { class RestrictUserForm { public static function selectUserForm( $val = null, $error = null ) { global $wgScript, $wgTitle; - $s = Xml::fieldset( wfMsg( 'restrictuser-userselect' ) ) . "<form action=\"{$wgScript}\">"; + $action = htmlspecialchars( $wgScript ); + $s = Xml::fieldset( wfMsg( 'restrictuser-userselect' ) ) . "<form action=\"{$action}\">"; if( $error ) $s .= '<p>' . $error . '</p>'; $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ); diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index 74b118e2..7fdb3cc4 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -7,87 +7,81 @@ * @ingroup SpecialPage */ -function wfSpecialRevisiondelete( $par = null ) { - global $wgOut, $wgRequest, $wgUser; - # Handle our many different possible input types - $target = $wgRequest->getText( 'target' ); - $oldid = $wgRequest->getArray( 'oldid' ); - $artimestamp = $wgRequest->getArray( 'artimestamp' ); - $logid = $wgRequest->getArray( 'logid' ); - $img = $wgRequest->getArray( 'oldimage' ); - $fileid = $wgRequest->getArray( 'fileid' ); - # For reviewing deleted files... - $file = $wgRequest->getVal( 'file' ); - # If this is a revision, then we need a target page - $page = Title::newFromUrl( $target ); - if( is_null($page) ) { - $wgOut->addWikiMsg( 'undelete-header' ); - return; - } - # Only one target set at a time please! - $i = (bool)$file + (bool)$oldid + (bool)$logid + (bool)$artimestamp + (bool)$fileid + (bool)$img; - if( $i !== 1 ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - # Logs must have a type given - if( $logid && !strpos($page->getDBKey(),'/') ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); - return; - } - # Either submit or create our form - $form = new RevisionDeleteForm( $page, $oldid, $logid, $artimestamp, $fileid, $img, $file ); - if( $wgRequest->wasPosted() ) { - $form->submit( $wgRequest ); - } else if( $oldid || $artimestamp ) { - $form->showRevs(); - } else if( $fileid || $img ) { - $form->showImages(); - } else if( $logid ) { - $form->showLogItems(); - } - # Show relevant lines from the deletion log. This will show even if said ID - # does not exist...might be helpful - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); - LogEventsList::showLogExtract( $wgOut, 'delete', $page->getPrefixedText() ); - if( $wgUser->isAllowed( 'suppressionlog' ) ){ - $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" ); - LogEventsList::showLogExtract( $wgOut, 'suppress', $page->getPrefixedText() ); - } -} +class SpecialRevisionDelete extends UnlistedSpecialPage { -/** - * Implements the GUI for Revision Deletion. - * @ingroup SpecialPage - */ -class RevisionDeleteForm { - /** - * @param Title $page - * @param array $oldids - * @param array $logids - * @param array $artimestamps - * @param array $fileids - * @param array $img - * @param string $file - */ - function __construct( $page, $oldids, $logids, $artimestamps, $fileids, $img, $file ) { - global $wgUser, $wgOut; + public function __construct() { + parent::__construct( 'Revisiondelete', 'deleterevision' ); + $this->includable( false ); + } - $this->page = $page; + public function execute( $par ) { + global $wgOut, $wgUser, $wgRequest; + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + if( !$wgUser->isAllowed( 'deleterevision' ) ) { + $wgOut->permissionRequired( 'deleterevision' ); + return; + } + $this->skin =& $wgUser->getSkin(); + # Set title and such + $this->setHeaders(); + $this->outputHeader(); + $this->wasPosted = $wgRequest->wasPosted(); + # Handle our many different possible input types + $this->target = $wgRequest->getText( 'target' ); + $this->oldids = $wgRequest->getArray( 'oldid' ); + $this->artimestamps = $wgRequest->getArray( 'artimestamp' ); + $this->logids = $wgRequest->getArray( 'logid' ); + $this->oldimgs = $wgRequest->getArray( 'oldimage' ); + $this->fileids = $wgRequest->getArray( 'fileid' ); # For reviewing deleted files... - if( $file ) { - $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $page, $file ); + $this->file = $wgRequest->getVal( 'file' ); + # Only one target set at a time please! + $i = (bool)$this->file + (bool)$this->oldids + (bool)$this->logids + + (bool)$this->artimestamps + (bool)$this->fileids + (bool)$this->oldimgs; + # No targets? + if( $i == 0 ) { + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); + return; + } + # Too many targets? + if( $i !== 1 ) { + $wgOut->showErrorPage( 'revdelete-toomanytargets-title', 'revdelete-toomanytargets-text' ); + return; + } + $this->page = Title::newFromUrl( $this->target ); + # If we have revisions, get the title from the first one + # since they should all be from the same page. This allows + # for more flexibility with page moves... + if( count($this->oldids) > 0 ) { + $rev = Revision::newFromId( $this->oldids[0] ); + $this->page = $rev ? $rev->getTitle() : $this->page; + } + # We need a target page! + if( is_null($this->page) ) { + $wgOut->addWikiMsg( 'undelete-header' ); + return; + } + # Logs must have a type given + if( $this->logids && !strpos($this->page->getDBKey(),'/') ) { + $wgOut->showErrorPage( 'revdelete-nologtype-title', 'revdelete-nologtype-text' ); + return; + } + # For reviewing deleted files...show it now if allowed + if( $this->file ) { + $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->page, $this->file ); $oimage->load(); // Check if user is allowed to see this file if( !$oimage->userCan(File::DELETED_FILE) ) { $wgOut->permissionRequired( 'suppressrevision' ); } else { - $this->showFile( $file ); + $this->showFile( $this->file ); } return; } - $this->skin = $wgUser->getSkin(); - # Give a link to the log for this page + # Give a link to the logs/hist for this page if( !is_null($this->page) && $this->page->getNamespace() > -1 ) { $links = array(); @@ -101,39 +95,65 @@ class RevisionDeleteForm { if( $wgUser->isAllowed('undelete') ) { $undelete = SpecialPage::getTitleFor( 'Undelete' ); $links[] = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'deletedhist' ), - wfArrayToCGI( array( 'target' => $this->page->getPrefixedUrl() ) ) ); + wfArrayToCGI( array( 'target' => $this->page->getPrefixedDBkey() ) ) ); } # Logs themselves don't have histories or archived revisions $wgOut->setSubtitle( '<p>'.implode($links,' / ').'</p>' ); } + # Lock the operation and the form context + $this->secureOperation(); + # Either submit or create our form + if( $this->wasPosted ) { + $this->submit( $wgRequest ); + } else if( $this->deleteKey == 'oldid' || $this->deleteKey == 'artimestamp' ) { + $this->showRevs(); + } else if( $this->deleteKey == 'fileid' || $this->deleteKey == 'oldimage' ) { + $this->showImages(); + } else if( $this->deleteKey == 'logid' ) { + $this->showLogItems(); + } + # Show relevant lines from the deletion log. This will show even if said ID + # does not exist...might be helpful + $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" ); + LogEventsList::showLogExtract( $wgOut, 'delete', $this->page->getPrefixedText() ); + if( $wgUser->isAllowed( 'suppressionlog' ) ){ + $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" ); + LogEventsList::showLogExtract( $wgOut, 'suppress', $this->page->getPrefixedText() ); + } + } + + private function secureOperation() { + global $wgUser; + $this->deleteKey = ''; // At this point, we should only have one of these - if( $oldids ) { - $this->revisions = $oldids; + if( $this->oldids ) { + $this->revisions = $this->oldids; $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); - $this->deleteKey='oldid'; - } else if( $artimestamps ) { - $this->archrevs = $artimestamps; + $this->deleteKey = 'oldid'; + } else if( $this->artimestamps ) { + $this->archrevs = $this->artimestamps; $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT ); - $this->deleteKey='artimestamp'; - } else if( $img ) { - $this->ofiles = $img; + $this->deleteKey = 'artimestamp'; + } else if( $this->oldimgs ) { + $this->ofiles = $this->oldimgs; $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); - $this->deleteKey='oldimage'; - } else if( $fileids ) { - $this->afiles = $fileids; + $this->deleteKey = 'oldimage'; + } else if( $this->fileids ) { + $this->afiles = $this->fileids; $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE ); - $this->deleteKey='fileid'; - } else if( $logids ) { - $this->events = $logids; + $this->deleteKey = 'fileid'; + } else if( $this->logids ) { + $this->events = $this->logids; $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogPage::DELETED_ACTION ); - $this->deleteKey='logid'; + $this->deleteKey = 'logid'; } // Our checkbox messages depends one what we are doing, // e.g. we don't hide "text" for logs or images $this->checks = array( $hide_content_name, array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ), - array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) ); + array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER ) + ); if( $wgUser->isAllowed('suppressrevision') ) { $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED ); } @@ -161,9 +181,8 @@ class RevisionDeleteForm { /** * This lets a user set restrictions for live and archived revisions */ - function showRevs() { - global $wgOut, $wgUser, $action; - + private function showRevs() { + global $wgOut, $wgUser; $UserAllowed = true; $count = ($this->deleteKey=='oldid') ? @@ -174,7 +193,7 @@ class RevisionDeleteForm { $wgOut->addHTML( "<ul>" ); $where = $revObjs = array(); - $dbr = wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_MASTER ); $revisions = 0; // Live revisions... @@ -183,10 +202,11 @@ class RevisionDeleteForm { foreach( $this->revisions as $revid ) { $where[] = intval($revid); } - $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; $result = $dbr->select( array('revision','page'), '*', - array( 'rev_page' => $this->page->getArticleID(), - $whereClause, 'rev_page = page_id' ), + array( + 'rev_page' => $this->page->getArticleID(), + 'rev_id' => $where, + 'rev_page = page_id' ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { $revObjs[$row->rev_id] = new Revision( $row ); @@ -197,7 +217,7 @@ class RevisionDeleteForm { continue; } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) { // If a rev is hidden from sysops - if( $action != 'submit') { + if( !$this->wasPosted ) { $wgOut->permissionRequired( 'suppressrevision' ); return; } @@ -211,34 +231,35 @@ class RevisionDeleteForm { } else { // Run through and pull all our data in one query foreach( $this->archrevs as $timestamp ) { - $where[] = $dbr->addQuotes( $timestamp ); + $where[] = $dbr->timestamp( $timestamp ); } - $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; $result = $dbr->select( 'archive', '*', - array( 'ar_namespace' => $this->page->getNamespace(), + array( + 'ar_namespace' => $this->page->getNamespace(), 'ar_title' => $this->page->getDBKey(), - $whereClause ), + 'ar_timestamp' => $where ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { - $revObjs[$row->ar_timestamp] = new Revision( array( - 'page' => $this->page->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => $row->ar_text_id, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); + $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp ); + $revObjs[$timestamp] = new Revision( array( + 'page' => $this->page->getArticleId(), + 'id' => $row->ar_rev_id, + 'text' => $row->ar_text_id, + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'text_id' => $row->ar_text_id, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len) ); } foreach( $this->archrevs as $timestamp ) { if( !isset($revObjs[$timestamp]) ) { continue; } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) { // If a rev is hidden from sysops - if( $action != 'submit') { + if( !$this->wasPosted ) { $wgOut->permissionRequired( 'suppressrevision' ); return; } @@ -255,8 +276,8 @@ class RevisionDeleteForm { } $wgOut->addHTML( "</ul>" ); - - $wgOut->addWikiMsg( 'revdelete-text' ); + // Explanation text + $this->addUsageText(); // Normal sysops can always see what they did, but can't always change it if( !$UserAllowed ) return; @@ -284,11 +305,8 @@ class RevisionDeleteForm { Xml::openElement( 'fieldset' ) . xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) ) ); - // FIXME: all items checked for just one rev are checked, even if not set for the others - foreach( $this->checks as $item ) { - list( $message, $name, $field ) = $item; - $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); - } + + $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) ); foreach( $items as $item ) { $wgOut->addHTML( Xml::tags( 'p', null, $item ) ); } @@ -299,39 +317,35 @@ class RevisionDeleteForm { Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n" ); - } /** * This lets a user set restrictions for archived images */ - function showImages() { - // What is $action doing here??? - global $wgOut, $wgUser, $action, $wgLang; - + private function showImages() { + global $wgOut, $wgUser, $wgLang; $UserAllowed = true; $count = ($this->deleteKey=='oldimage') ? count($this->ofiles) : count($this->afiles); - $wgOut->addWikiMsg( 'revdelete-selected', - $this->page->getPrefixedText(), + $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $wgLang->formatNum($count) ); $bitfields = 0; $wgOut->addHTML( "<ul>" ); $where = $filesObjs = array(); - $dbr = wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_MASTER ); // Live old revisions... $revisions = 0; if( $this->deleteKey=='oldimage' ) { // Run through and pull all our data in one query foreach( $this->ofiles as $timestamp ) { - $where[] = $dbr->addQuotes( $timestamp.'!'.$this->page->getDBKey() ); + $where[] = $timestamp.'!'.$this->page->getDBKey(); } - $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; $result = $dbr->select( 'oldimage', '*', - array( 'oi_name' => $this->page->getDBKey(), - $whereClause ), + array( + 'oi_name' => $this->page->getDBKey(), + 'oi_archive_name' => $where ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); @@ -345,7 +359,7 @@ class RevisionDeleteForm { continue; } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) { // If a rev is hidden from sysops - if( $action != 'submit' ) { + if( !$this->wasPosted ) { $wgOut->permissionRequired( 'suppressrevision' ); return; } @@ -362,10 +376,10 @@ class RevisionDeleteForm { foreach( $this->afiles as $id ) { $where[] = intval($id); } - $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; $result = $dbr->select( 'filearchive', '*', - array( 'fa_name' => $this->page->getDBKey(), - $whereClause ), + array( + 'fa_name' => $this->page->getDBKey(), + 'fa_id' => $where ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); @@ -376,7 +390,7 @@ class RevisionDeleteForm { continue; } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) { // If a rev is hidden from sysops - if( $action != 'submit' ) { + if( !$this->wasPosted ) { $wgOut->permissionRequired( 'suppressrevision' ); return; } @@ -394,9 +408,9 @@ class RevisionDeleteForm { } $wgOut->addHTML( "</ul>" ); - - $wgOut->addWikiMsg('revdelete-text' ); - //Normal sysops can always see what they did, but can't always change it + // Explanation text + $this->addUsageText(); + // Normal sysops can always see what they did, but can't always change it if( !$UserAllowed ) return; $items = array( @@ -421,11 +435,8 @@ class RevisionDeleteForm { 'id' => 'mw-revdel-form-filerevisions' ) ) . Xml::fieldset( wfMsg( 'revdelete-legend' ) ) ); - // FIXME: all items checked for just one file are checked, even if not set for the others - foreach( $this->checks as $item ) { - list( $message, $name, $field ) = $item; - $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); - } + + $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) ); foreach( $items as $item ) { $wgOut->addHTML( "<p>$item</p>" ); } @@ -442,26 +453,27 @@ class RevisionDeleteForm { /** * This lets a user set restrictions for log items */ - function showLogItems() { - global $wgOut, $wgUser, $action, $wgMessageCache, $wgLang; - + private function showLogItems() { + global $wgOut, $wgUser, $wgMessageCache, $wgLang; $UserAllowed = true; + $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->events) ) ); $bitfields = 0; $wgOut->addHTML( "<ul>" ); $where = $logRows = array(); - $dbr = wfGetDB( DB_SLAVE ); + $dbr = wfGetDB( DB_MASTER ); // Run through and pull all our data in one query $logItems = 0; foreach( $this->events as $logid ) { $where[] = intval($logid); } list($log,$logtype) = explode( '/',$this->page->getDBKey(), 2 ); - $whereClause = "log_type = '$logtype' AND log_id IN(" . implode(',',$where) . ")"; $result = $dbr->select( 'logging', '*', - array( $whereClause ), + array( + 'log_type' => $logtype, + 'log_id' => $where ), __METHOD__ ); while( $row = $dbr->fetchObject( $result ) ) { $logRows[$row->log_id] = $row; @@ -473,7 +485,7 @@ class RevisionDeleteForm { continue; } else if( !LogEventsList::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) { // If an event is hidden from sysops - if( $action != 'submit') { + if( !$this->wasPosted ) { $wgOut->permissionRequired( 'suppressrevision' ); return; } @@ -484,13 +496,13 @@ class RevisionDeleteForm { $bitfields |= $logRows[$logid]->log_deleted; } if( !$logItems ) { - $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' ); + $wgOut->showErrorPage( 'revdelete-nologid-title', 'revdelete-nologid-text' ); return; } $wgOut->addHTML( "</ul>" ); - - $wgOut->addWikiMsg( 'revdelete-text' ); + // Explanation text + $this->addUsageText(); // Normal sysops can always see what they did, but can't always change it if( !$UserAllowed ) return; @@ -511,11 +523,8 @@ class RevisionDeleteForm { 'id' => 'mw-revdel-form-logs' ) ) . Xml::fieldset( wfMsg( 'revdelete-legend' ) ) ); - // FIXME: all items checked for just on event are checked, even if not set for the others - foreach( $this->checks as $item ) { - list( $message, $name, $field ) = $item; - $wgOut->addHTML( Xml::tags( 'div', null, Xml::checkLabel( wfMsg( $message ), $name, $name, $bitfields & $field ) ) ); - } + + $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) ); foreach( $items as $item ) { $wgOut->addHTML( "<p>$item</p>" ); } @@ -528,21 +537,47 @@ class RevisionDeleteForm { Xml::closeElement( 'form' ) . "\n" ); } + + private function addUsageText() { + global $wgOut, $wgUser; + $wgOut->addWikiMsg( 'revdelete-text' ); + if( $wgUser->isAllowed( 'suppressrevision' ) ) { + $wgOut->addWikiMsg( 'revdelete-suppress-text' ); + } + } + + /** + * @param int $bitfields, aggregate bitfield of all the bitfields + * @returns string HTML + */ + private function buildCheckBoxes( $bitfields ) { + $html = ''; + // FIXME: all items checked for just one rev are checked, even if not set for the others + foreach( $this->checks as $item ) { + list( $message, $name, $field ) = $item; + $line = Xml::tags( 'div', null, Xml::checkLabel( wfMsg($message), $name, $name, + $bitfields & $field ) ); + if( $field == Revision::DELETED_RESTRICTED ) $line = "<b>$line</b>"; + $html .= $line; + } + return $html; + } /** * @param Revision $rev * @returns string */ private function historyLine( $rev ) { - global $wgLang; + global $wgLang, $wgUser; $date = $wgLang->timeanddate( $rev->getTimestamp() ); $difflink = $del = ''; // Live revisions if( $this->deleteKey=='oldid' ) { - $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() ); + $tokenParams = '&unhide=1&token='.urlencode( $wgUser->editToken( $rev->getId() ) ); + $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid='.$rev->getId() . $tokenParams ); $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'), - 'diff=' . $rev->getId() . '&oldid=prev' ) . ')'; + 'diff=' . $rev->getId() . '&oldid=prev' . $tokenParams ) . ')'; // Archived revisions } else { $undelete = SpecialPage::getTitleFor( 'Undelete' ); @@ -552,7 +587,7 @@ class RevisionDeleteForm { $difflink = '(' . $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml('diff'), "target=$target&diff=prev×tamp=" . $rev->getTimestamp() ) . ')'; } - + // Check permissions; items may be "suppressed" if( $rev->isDeleted(Revision::DELETED_TEXT) ) { $revlink = '<span class="history-deleted">'.$revlink.'</span>'; $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>'; @@ -561,8 +596,10 @@ class RevisionDeleteForm { $difflink = '(' . wfMsgHtml('diff') . ')'; } } + $userlink = $this->skin->revUserLink( $rev ); + $comment = $this->skin->revComment( $rev ); - return "<li> $difflink $revlink ".$this->skin->revUserLink( $rev )." ".$this->skin->revComment( $rev )."$del</li>"; + return "<li>$difflink $revlink $userlink $comment{$del}</li>"; } /** @@ -704,9 +741,13 @@ class RevisionDeleteForm { /** * @param WebRequest $request */ - function submit( $request ) { + private function submit( $request ) { global $wgUser, $wgOut; - + # Check edit token on submission + if( $this->wasPosted && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) { + $wgOut->addWikiMsg( 'sessionfailure' ); + return false; + } $bitfield = $this->extractBitfield( $request ); $comment = $request->getText( 'wpReason' ); # Can the user set this field? @@ -812,10 +853,10 @@ class RevisionDeleter { foreach( $items as $revid ) { $where[] = intval($revid); } - $whereClause = 'rev_id IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'revision', '*', - array( 'rev_page' => $title->getArticleID(), - $whereClause ), + array( + 'rev_page' => $title->getArticleID(), + 'rev_id' => $where ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { $revObjs[$row->rev_id] = new Revision( $row ); @@ -869,27 +910,28 @@ class RevisionDeleter { $Id_set = array(); // Run through and pull all our data in one query foreach( $items as $timestamp ) { - $where[] = $this->dbw->addQuotes( $timestamp ); + $where[] = $this->dbw->timestamp( $timestamp ); } - $whereClause = 'ar_timestamp IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'archive', '*', - array( 'ar_namespace' => $title->getNamespace(), + array( + 'ar_namespace' => $title->getNamespace(), 'ar_title' => $title->getDBKey(), - $whereClause ), + 'ar_timestamp' => $where ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { - $revObjs[$row->ar_timestamp] = new Revision( array( - 'page' => $title->getArticleId(), - 'id' => $row->ar_rev_id, - 'text' => $row->ar_text_id, - 'comment' => $row->ar_comment, - 'user' => $row->ar_user, - 'user_text' => $row->ar_user_text, - 'timestamp' => $row->ar_timestamp, - 'minor_edit' => $row->ar_minor_edit, - 'text_id' => $row->ar_text_id, - 'deleted' => $row->ar_deleted, - 'len' => $row->ar_len) ); + $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp ); + $revObjs[$timestamp] = new Revision( array( + 'page' => $title->getArticleId(), + 'id' => $row->ar_rev_id, + 'text' => $row->ar_text_id, + 'comment' => $row->ar_comment, + 'user' => $row->ar_user, + 'user_text' => $row->ar_user_text, + 'timestamp' => $timestamp, + 'minor_edit' => $row->ar_minor_edit, + 'text_id' => $row->ar_text_id, + 'deleted' => $row->ar_deleted, + 'len' => $row->ar_len) ); } // To work! foreach( $items as $timestamp ) { @@ -939,12 +981,12 @@ class RevisionDeleter { $set = array(); // Run through and pull all our data in one query foreach( $items as $timestamp ) { - $where[] = $this->dbw->addQuotes( $timestamp.'!'.$title->getDBKey() ); + $where[] = $timestamp.'!'.$title->getDBKey(); } - $whereClause = 'oi_archive_name IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'oldimage', '*', - array( 'oi_name' => $title->getDBKey(), - $whereClause ), + array( + 'oi_name' => $title->getDBKey(), + 'oi_archive_name' => $where ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row ); @@ -1034,10 +1076,9 @@ class RevisionDeleter { foreach( $items as $id ) { $where[] = intval($id); } - $whereClause = 'fa_id IN(' . implode(',',$where) . ')'; $result = $this->dbw->select( 'filearchive', '*', array( 'fa_name' => $title->getDBKey(), - $whereClause ), + 'fa_id' => $where ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row ); @@ -1091,9 +1132,10 @@ class RevisionDeleter { $where[] = intval($logid); } list($log,$logtype) = explode( '/',$title->getDBKey(), 2 ); - $whereClause = "log_type ='$logtype' AND log_id IN(" . implode(',',$where) . ")"; $result = $this->dbw->select( 'logging', '*', - array( $whereClause ), + array( + 'log_type' => $logtype, + 'log_id' => $where ), __METHOD__ ); while( $row = $this->dbw->fetchObject( $result ) ) { $logRows[$row->log_id] = $row; diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php index f3117242..cb783819 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -41,7 +41,7 @@ function wfSpecialSearch( $par = '' ) { || !is_null( $wgRequest->getVal( 'offset' )) || !is_null( $wgRequest->getVal( 'searchx' )) ) { - $searchPage->showResults( $search, 'search' ); + $searchPage->showResults( $search ); } else { $searchPage->goResult( $search ); } @@ -74,6 +74,7 @@ class SpecialSearch { $this->active = 'advanced'; $this->sk = $user->getSkin(); $this->didYouMeanHtml = ''; # html of did you mean... link + $this->fulltext = $request->getVal('fulltext'); } /** @@ -163,9 +164,13 @@ class SpecialSearch { // did you mean... suggestions if( $textMatches && $textMatches->hasSuggestion() ) { - $st = SpecialPage::getTitleFor( 'Search' ); + $st = SpecialPage::getTitleFor( 'Search' ); + # mirror Go/Search behaviour of original request .. + $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() ); + if($this->fulltext != NULL) + $didYouMeanParams['fulltext'] = $this->fulltext; $stParams = wfArrayToCGI( - array( 'search' => $textMatches->getSuggestionQuery(), 'fulltext' => wfMsg('search') ), + $didYouMeanParams, $this->powerSearchOptions() ); $suggestLink = $sk->makeKnownLinkObj( $st, @@ -610,7 +615,8 @@ class SpecialSearch { $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) ); $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' ); - $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) ); + $searchField = Xml::inputLabel( wfMsg('powersearch-field'), 'search', 'powerSearchText', 50, $term, + array( 'type' => 'text') ); $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' )) . "\n"; $searchTitle = SpecialPage::getTitleFor( 'Search' ); @@ -630,10 +636,9 @@ class SpecialSearch { "<hr style=\"clear: both;\" />\n". $redirectText ."\n". "<div style=\"padding-top:2px;padding-bottom:2px;\">". - wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) . - " " . $searchField . " " . + Xml::hidden( 'fulltext', 'Advanced search' ) . "\n" . $searchButton . "</div>". "</form>"; @@ -666,7 +671,7 @@ class SpecialSearch { } protected function formHeader( $term ) { - global $wgContLang, $wgCanonicalNamespaceNames; + global $wgContLang, $wgCanonicalNamespaceNames, $wgLang; $sep = ' '; $out = Xml::openElement('div', array( 'style' => 'padding-bottom:0.5em;' ) ); @@ -680,7 +685,7 @@ class SpecialSearch { // search profiles headers $m = wfMsg( 'searchprofile-articles' ); $tt = wfMsg( 'searchprofile-articles-tooltip', - implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultNamespaces() ) ) ); + $wgLang->commaList( SearchEngine::namespacesAsText( SearchEngine::defaultNamespaces() ) ) ); if( $this->active == 'default' ) { $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); } else { @@ -697,22 +702,10 @@ class SpecialSearch { $out .= $this->makeSearchLink( $imageTextForm, array( NS_FILE ) , $m, $tt ); } $out .= $sep; - - /* - $m = wfMsg( 'searchprofile-articles-and-proj' ); - $tt = wfMsg( 'searchprofile-project-tooltip', - implode( ', ', SearchEngine::namespacesAsText( SearchEngine::defaultAndProjectNamespaces() ) ) ); - if( $this->active == 'withproject' ) { - $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); - } else { - $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultAndProjectNamespaces(), $m, $tt ); - } - $out .= $sep; - */ - + $m = wfMsg( 'searchprofile-project' ); $tt = wfMsg( 'searchprofile-project-tooltip', - implode( ', ', SearchEngine::namespacesAsText( SearchEngine::projectNamespaces() ) ) ); + $wgLang->commaList( SearchEngine::namespacesAsText( SearchEngine::projectNamespaces() ) ) ); if( $this->active == 'project' ) { $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m ); } else { @@ -765,6 +758,7 @@ class SpecialSearch { $out .= Xml::hidden( "redirs", (int)$this->searchRedirects ); // Term box $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . "\n"; + $out .= Xml::hidden( 'fulltext', 'Search' ); $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); $out .= ' (' . wfMsgExt('searchmenu-help',array('parseinline') ) . ')'; $out .= Xml::closeElement( 'form' ); @@ -867,6 +861,7 @@ class SpecialSearchOld { } $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; + $this->fulltext = $request->getVal('fulltext'); } /** @@ -906,21 +901,21 @@ class SpecialSearchOld { } } - $wgOut->wrapWikiMsg( "==$1==\n", 'notitlematches' ); + $extra = $wgOut->parse( '=='.wfMsgNoTrans( 'notitlematches' )."==\n" ); if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) { - $wgOut->addWikiMsg( 'noexactmatch', wfEscapeWikiText( $term ) ); + $extra .= wfMsgExt( 'noexactmatch', 'parse', wfEscapeWikiText( $term ) ); } else { - $wgOut->addWikiMsg( 'noexactmatch-nocreate', wfEscapeWikiText( $term ) ); + $extra .= wfMsgExt( 'noexactmatch-nocreate', 'parse', wfEscapeWikiText( $term ) ); } - return $this->showResults( $term ); + $this->showResults( $term, $extra ); } /** * @param string $term - * @public + * @param string $extra Extra HTML to add after "did you mean" */ - function showResults( $term ) { + public function showResults( $term, $extra = '' ) { wfProfileIn( __METHOD__ ); global $wgOut, $wgUser; $sk = $wgUser->getSkin(); @@ -931,7 +926,7 @@ class SpecialSearchOld { $search->showRedirects = $this->searchRedirects; $search->prefix = $this->mPrefix; $term = $search->transformSearchTerm($term); - + $this->setupPage( $term ); $rewritten = $search->replacePrefixes($term); @@ -940,20 +935,27 @@ class SpecialSearchOld { // did you mean... suggestions if($textMatches && $textMatches->hasSuggestion()){ - $st = SpecialPage::getTitleFor( 'Search' ); - $stParams = wfArrayToCGI( array( - 'search' => $textMatches->getSuggestionQuery(), - 'fulltext' => wfMsg('search')), - $this->powerSearchOptions()); - + $st = SpecialPage::getTitleFor( 'Search' ); + + # mirror Go/Search behaviour of original request + $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() ); + if($this->fulltext != NULL) + $didYouMeanParams['fulltext'] = $this->fulltext; + $stParams = wfArrayToCGI( + $didYouMeanParams, + $this->powerSearchOptions() + ); + $suggestLink = $sk->makeKnownLinkObj( $st, $textMatches->getSuggestionSnippet(), $stParams ); - + $wgOut->addHTML('<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>'); } - $wgOut->addWikiMsg( 'searchresulttext' ); + $wgOut->addHTML( $extra ); + + $wgOut->wrapWikiMsg( "<div class='mw-searchresult'>\n$1</div>", 'searchresulttext' ); if( '' === trim( $term ) ) { // Empty query -- straight view of search form @@ -1428,10 +1430,11 @@ class SpecialSearchOld { $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) ); $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' ) ) . "\n"; $searchTitle = SpecialPage::getTitleFor( 'Search' ); + $searchHiddens = Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n"; + $searchHiddens .= Xml::hidden( 'fulltext', 'Advanced search' ) . "\n"; $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) . - Xml::fieldset( wfMsg( 'powersearch-legend' ), - Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n" . + Xml::fieldset( wfMsg( 'powersearch-legend' ), "<p>" . wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . "</p>\n" . @@ -1444,6 +1447,7 @@ class SpecialSearchOld { " " . $searchField . " " . + $searchHiddens . $searchButton ) . "</form>"; @@ -1468,13 +1472,14 @@ class SpecialSearchOld { 'action' => $wgScript )); $searchTitle = SpecialPage::getTitleFor( 'Search' ); - $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ); $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . ' '; foreach( SearchEngine::searchableNamespaces() as $ns => $name ) { if( in_array( $ns, $this->namespaces ) ) { $out .= Xml::hidden( "ns{$ns}", '1' ); } } + $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ); + $out .= Xml::hidden( 'fulltext', 'Search' ); $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) ); $out .= Xml::closeElement( 'form' ); diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index 560ba445..4959f107 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -48,19 +48,21 @@ function wfSpecialSpecialpages() { $groups['other'] = $other; } + $includesRestrictedPages = false; /** Now output the HTML */ foreach ( $groups as $group => $sortedPages ) { $middle = ceil( count($sortedPages)/2 ); $total = count($sortedPages); $count = 0; - $wgOut->addHTML( "<h4 class='mw-specialpagesgroup'>".wfMsgHtml("specialpages-group-$group")."</h4>\n" ); + $wgOut->wrapWikiMsg( "<h4 class='mw-specialpagesgroup'>$1</h4>\n", "specialpages-group-$group" ); $wgOut->addHTML( "<table style='width: 100%;' class='mw-specialpages-table'><tr>" ); $wgOut->addHTML( "<td width='30%' valign='top'><ul>\n" ); foreach( $sortedPages as $desc => $specialpage ) { list( $title, $restricted ) = $specialpage; $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) ); if( $restricted ) { + $includesRestrictedPages = true; $wgOut->addHTML( "<li class='mw-specialpages-page mw-specialpagerestricted'>{$link}</li>\n" ); } else { $wgOut->addHTML( "<li>{$link}</li>\n" ); @@ -74,9 +76,8 @@ function wfSpecialSpecialpages() { } $wgOut->addHTML( "</ul></td><td width='30%' valign='top'></td></tr></table>\n" ); } - $wgOut->addHTML( - Xml::openElement('div', array( 'class' => 'mw-specialpages-notes' )). - wfMsgWikiHtml('specialpages-note'). - Xml::closeElement('div') - ); + + if ( $includesRestrictedPages ) { + $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' ); + } } diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php new file mode 100644 index 00000000..981eb2ff --- /dev/null +++ b/includes/specials/SpecialTags.php @@ -0,0 +1,73 @@ +<?php + +if (!defined('MEDIAWIKI')) + die; + +class SpecialTags extends SpecialPage { + + function __construct() { + parent::__construct( 'Tags' ); + } + + function execute( $par ) { + global $wgOut, $wgUser, $wgMessageCache; + + $wgMessageCache->loadAllMessages(); + + $sk = $wgUser->getSkin(); + $wgOut->setPageTitle( wfMsg( 'tags-title' ) ); + $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1</div>", 'tags-intro' ); + + // Write the headers + $html = ''; + $html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) . + Xml::tags( 'th', null, wfMsgExt( 'tags-display-header', 'parseinline' ) ) . + Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) . + Xml::tags( 'th', null, wfMsgExt( 'tags-hitcount-header', 'parseinline' ) ) + ); + $dbr = wfGetDB( DB_SLAVE ); + $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); + + while ( $row = $res->fetchObject() ) { + $html .= $this->doTagRow( $row->ct_tag, $row->hitcount ); + } + + foreach( ChangeTags::listDefinedTags() as $tag ) { + $html .= $this->doTagRow( $tag, 0 ); + } + + $wgOut->addHTML( Xml::tags( 'table', array( 'class' => 'mw-tags-table' ), $html ) ); + } + + function doTagRow( $tag, $hitcount ) { + static $sk=null, $doneTags=array(); + if (!$sk) { + global $wgUser; + $sk = $wgUser->getSkin(); + } + + if ( in_array( $tag, $doneTags ) ) { + return ''; + } + + $newRow = ''; + $newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) ); + + $disp = ChangeTags::tagDescription( $tag ); + $disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsg( 'tags-edit' ) ) . ')'; + $newRow .= Xml::tags( 'td', null, $disp ); + + $desc = wfMsgExt( "tag-$tag-description", 'parseinline' ); + $desc = wfEmptyMsg( "tag-$tag-description", $desc ) ? '' : $desc; + $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsg( 'tags-edit' ) ) . ')'; + $newRow .= Xml::tags( 'td', null, $desc ); + + $hitcount = wfMsg( 'tags-hitcount', $hitcount ); + $hitcount = $sk->link( SpecialPage::getTitleFor( 'RecentChanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) ); + $newRow .= Xml::tags( 'td', null, $hitcount ); + + $doneTags[] = $tag; + + return Xml::tags( 'tr', null, $newRow ) . "\n"; + } +}
\ No newline at end of file diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index a9fb4ef1..d97efb59 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -737,10 +737,10 @@ class UndeleteForm { if( $rev->isDeleted(Revision::DELETED_TEXT) ) { if( !$rev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); return; } else { - $wgOut->addWikiText( wfMsg( 'rev-deleted-text-view' ) ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); $wgOut->addHTML( '<br/>' ); // and we are allowed to see... } @@ -996,6 +996,11 @@ class UndeleteForm { # Show relevant lines from the deletion log: $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" ); LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() ); + # Show relevant lines from the suppression log: + if( $wgUser->isAllowed( 'suppressionlog' ) ) { + $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" ); + LogEventsList::showLogExtract( $wgOut, 'suppress', $this->mTargetObj->getPrefixedText() ); + } if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { # Format the user-visible controls (comment field, submission button) @@ -1132,19 +1137,15 @@ class UndeleteForm { $comment = $sk->revComment( $rev ); $revdlink = ''; if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { // If revision was hidden from sysops - $del = wfMsgHtml('rev-delundel'); + $revdlink = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml('rev-delundel').')' ); } else { - $del = $sk->makeKnownLinkObj( $revdel, - wfMsgHtml('rev-delundel'), - 'target=' . $this->mTargetObj->getPrefixedUrl() . "&artimestamp=$ts" ); - // Bolden oversighted content - if( $rev->isDeleted( Revision::DELETED_RESTRICTED ) ) - $del = "<strong>$del</strong>"; + $query = array( 'target' => $this->mTargetObj->getPrefixedDBkey(), + 'artimestamp[]' => $ts + ); + $revdlink = $sk->revDeleteLink( $query, $rev->isDeleted( Revision::DELETED_RESTRICTED ) ); } - $revdlink = "<tt>(<small>$del</small>)</tt>"; } return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>"; @@ -1178,20 +1179,15 @@ class UndeleteForm { $comment = $this->getFileComment( $file, $sk ); $revdlink = ''; if( $wgUser->isAllowed( 'deleterevision' ) ) { - $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); if( !$file->userCan(File::DELETED_RESTRICTED ) ) { // If revision was hidden from sysops - $del = wfMsgHtml('rev-delundel'); + $revdlink = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml('rev-delundel').')' ); } else { - $del = $sk->makeKnownLinkObj( $revdel, - wfMsgHtml('rev-delundel'), - 'target=' . $this->mTargetObj->getPrefixedUrl() . - '&fileid=' . $row->fa_id ); - // Bolden oversighted content - if( $file->isDeleted( File::DELETED_RESTRICTED ) ) - $del = "<strong>$del</strong>"; + $query = array( 'target' => $this->mTargetObj->getPrefixedDBkey(), + 'fileid' => $row->fa_id + ); + $revdlink = $sk->revDeleteLink( $query, $file->isDeleted( File::DELETED_RESTRICTED ) ); } - $revdlink = "<tt>(<small>$del</small>)</tt>"; } return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n"; } diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php index 0bf7e5aa..a3e8a0c4 100644 --- a/includes/specials/SpecialUnlockdb.php +++ b/includes/specials/SpecialUnlockdb.php @@ -98,10 +98,9 @@ END function showSuccess() { global $wgOut; - global $ip; $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) ); - $wgOut->addWikiMsg( "unlockdbsuccesstext", $ip ); + $wgOut->addWikiMsg( "unlockdbsuccesstext" ); } } diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php index 4adf405d..fa66555d 100644 --- a/includes/specials/SpecialUnusedimages.php +++ b/includes/specials/SpecialUnusedimages.php @@ -22,13 +22,17 @@ class UnusedimagesPage extends ImageQueryPage { function isSyndicated() { return false; } function getSQL() { - global $wgCountCategorizedImagesAsUsed; + global $wgCountCategorizedImagesAsUsed, $wgDBtype; $dbr = wfGetDB( DB_SLAVE ); + $epoch = $wgDBtype == 'mysql' ? + 'UNIX_TIMESTAMP(img_timestamp)' : + 'EXTRACT(epoch FROM img_timestamp)'; + if ( $wgCountCategorizedImagesAsUsed ) { list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' ); - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, + return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value, img_user, img_user_text, img_description FROM ((($page AS I LEFT JOIN $categorylinks AS L ON I.page_id = L.cl_from) LEFT JOIN $imagelinks AS P ON I.page_title = P.il_to) @@ -37,14 +41,14 @@ class UnusedimagesPage extends ImageQueryPage { } else { list( $image, $imagelinks ) = $dbr->tableNamesN( 'image','imagelinks' ); - return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, img_timestamp as value, + return "SELECT 'Unusedimages' as type, 6 as namespace, img_name as title, $epoch as value, img_user, img_user_text, img_description FROM $image LEFT JOIN $imagelinks ON img_name=il_to WHERE il_to IS NULL "; } } function getPageHeader() { - return wfMsgExt( 'unusedimagestext', array( 'parse') ); + return wfMsgExt( 'unusedimagestext', array( 'parse' ) ); } } diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 450c8728..4c5bb160 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -62,6 +62,8 @@ class UploadForm { $this->mDesiredDestName = $request->getText( 'wpDestFile' ); $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' ); $this->mComment = $request->getText( 'wpUploadDescription' ); + $this->mForReUpload = $request->getBool( 'wpForReUpload' ); + $this->mReUpload = $request->getCheck( 'wpReUpload' ); if( !$request->wasPosted() ) { # GET requests just give the main form; no data except destination @@ -72,8 +74,6 @@ class UploadForm { # Placeholders for text injection by hooks (empty per default) $this->uploadFormTextTop = ""; $this->uploadFormTextAfterSummary = ""; - - $this->mReUpload = $request->getCheck( 'wpReUpload' ); $this->mUploadClicked = $request->getCheck( 'wpUpload' ); $this->mLicense = $request->getText( 'wpLicense' ); @@ -155,7 +155,7 @@ class UploadForm { * Returns true if there was an error, false otherwise */ private function curlCopy( $url, $dest ) { - global $wgUser, $wgOut; + global $wgUser, $wgOut, $wgHTTPProxy; if( !$wgUser->isAllowed( 'upload_by_url' ) ) { $wgOut->permissionRequired( 'upload_by_url' ); @@ -183,6 +183,9 @@ class UploadForm { curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed curl_setopt( $ch, CURLOPT_URL, $url); + if( $wgHTTPProxy ) { + curl_setopt( $ch, CURLOPT_PROXY, $wgHTTPProxy ); + } curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) ); curl_exec( $ch ); $error = curl_errno( $ch ) ? true : false; @@ -228,6 +231,12 @@ class UploadForm { global $wgUser, $wgOut; global $wgEnableUploads; + # Check php's file_uploads setting + if( !wfIniGetBool( 'file_uploads' ) ) { + $wgOut->showErrorPage( 'uploaddisabled', 'php-uploaddisabledtext', array( $this->mDesiredDestName ) ); + return; + } + # Check uploading enabled if( !$wgEnableUploads ) { $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) ); @@ -372,7 +381,7 @@ class UploadForm { if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { - wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file." ); + wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); return self::BEFORE_PROCESSING; } @@ -504,11 +513,13 @@ class UploadForm { if ( ! $this->mIgnoreWarning ) { $warning = ''; - global $wgCapitalLinks; - if( $wgCapitalLinks ) { - $filtered = ucfirst( $filtered ); + $comparableName = str_replace( ' ', '_', $basename ); + global $wgCapitalLinks, $wgContLang; + if ( $wgCapitalLinks ) { + $comparableName = $wgContLang->ucfirst( $comparableName ); } - if( $basename != $filtered ) { + + if( $comparableName !== $filtered ) { $warning .= '<li>'.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'</li>'; } @@ -541,7 +552,7 @@ class UploadForm { $warning .= self::getExistsWarning( $this->mLocalFile ); } - $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt ); + $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt, $nt ); if( $warning != '' ) { /** @@ -557,8 +568,10 @@ class UploadForm { * Try actually saving the thing... * It will show an error form on failure. */ - $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, - $this->mCopyrightStatus, $this->mCopyrightSource ); + if( !$this->mForReUpload ) { + $pageText = self::getInitialPageText( $this->mComment, $this->mLicense, + $this->mCopyrightStatus, $this->mCopyrightSource ); + } $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText, File::DELETE_SOURCE, $this->mFileProps ); @@ -748,7 +761,7 @@ class UploadForm { * Check for duplicate files and throw up a warning before the upload * completes. */ - function getDupeWarning( $tempfile, $extension ) { + function getDupeWarning( $tempfile, $extension, $destinationTitle ) { $hash = File::sha1Base36( $tempfile ); $dupes = RepoGroup::singleton()->findBySha1( $hash ); $archivedImage = new ArchivedFile( null, 0, $hash.".$extension" ); @@ -757,8 +770,12 @@ class UploadForm { $msg = "<gallery>"; foreach( $dupes as $file ) { $title = $file->getTitle(); - $msg .= $title->getPrefixedText() . - "|" . $title->getText() . "\n"; + # Don't throw the warning when the titles are the same, it's a reupload + # and highly redundant. + if ( !$title->equals( $destinationTitle ) || !$this->mForReUpload ) { + $msg .= $title->getPrefixedText() . + "|" . $title->getText() . "\n"; + } } $msg .= "</gallery>"; return "<li>" . @@ -860,6 +877,7 @@ class UploadForm { */ function unsaveUploadedFile() { global $wgOut; + if( !$this->mTempPath ) return true; // nothing to delete $repo = RepoGroup::singleton()->getLocalRepo(); $success = $repo->freeTemp( $this->mTempPath ); if ( ! $success ) { @@ -958,7 +976,7 @@ wgUploadAutoFill = {$autofill}; if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { - wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); + wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" ); return false; } @@ -978,7 +996,7 @@ wgUploadAutoFill = {$autofill}; } // Show the relevant lines from deletion log (for still deleted files only) - if( $title instanceof Title && $title->isDeleted() > 0 && !$title->exists() ) { + if( $title instanceof Title && $title->isDeletedQuick() && !$title->exists() ) { $this->showDeletionLog( $wgOut, $title->getPrefixedText() ); } } @@ -1054,7 +1072,8 @@ wgUploadAutoFill = {$autofill}; $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) ); $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) ); - $summary = wfMsgExt( 'fileuploadsummary', 'parseinline' ); + $msg = $this->mForReUpload ? 'filereuploadsummary' : 'fileuploadsummary'; + $summary = wfMsgExt( $msg, 'parseinline' ); $licenses = new Licenses(); $license = wfMsgExt( 'license', array( 'parseinline' ) ); @@ -1068,10 +1087,9 @@ wgUploadAutoFill = {$autofill}; $encDestName = htmlspecialchars( $this->mDesiredDestName ); - $watchChecked = $this->watchCheck() - ? 'checked="checked"' - : ''; - $warningChecked = $this->mIgnoreWarning ? 'checked' : ''; + $watchChecked = $this->watchCheck() ? 'checked="checked"' : ''; + # Re-uploads should not need "file exist already" warnings + $warningChecked = ($this->mIgnoreWarning || $this->mForReUpload) ? 'checked="checked"' : ''; // Prepare form for upload or upload/copy if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) { @@ -1106,8 +1124,8 @@ wgUploadAutoFill = {$autofill}; $warningRow = ''; $destOnkeyup = ''; } - $encComment = htmlspecialchars( $this->mComment ); + $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(), @@ -1135,10 +1153,26 @@ wgUploadAutoFill = {$autofill}; <td class='mw-label'> <label for='wpDestFile'>{$destfilename}</label> </td> - <td class='mw-input'> - <input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60' - value=\"{$encDestName}\" onchange='toggleFilenameFiller()' $destOnkeyup /> - </td> + <td class='mw-input'>" + ); + if( $this->mForReUpload ) { + $wgOut->addHTML( + Xml::hidden( 'wpDestFile', $this->mDesiredDestName, array('id'=>'wpDestFile','tabindex'=>2) ) . + "<tt>" . + $encDestName . + "</tt>" + ); + } + else { + $wgOut->addHTML( + "<input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60' + value=\"{$encDestName}\" onchange='toggleFilenameFiller()' $destOnkeyup />" + ); + } + + + $wgOut->addHTML( + "</td> </tr> <tr> <td class='mw-label'> @@ -1152,8 +1186,8 @@ wgUploadAutoFill = {$autofill}; </tr> <tr>" ); - - if ( $licenseshtml != '' ) { + # Re-uploads should not need license info + if ( !$this->mForReUpload && $licenseshtml != '' ) { global $wgStylePath; $wgOut->addHTML( " <td class='mw-label'> @@ -1179,7 +1213,7 @@ wgUploadAutoFill = {$autofill}; } } - if ( $wgUseCopyrightUpload ) { + if ( !$this->mForReUpload && $wgUseCopyrightUpload ) { $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' ); $copystatus = htmlspecialchars( $this->mCopyrightStatus ); $filesource = wfMsgExt( 'filesource', 'escapenoentities' ); @@ -1211,7 +1245,7 @@ wgUploadAutoFill = {$autofill}; <td> <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' /> <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label> - <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked/> + <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked /> <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label> </td> </tr> @@ -1219,19 +1253,23 @@ wgUploadAutoFill = {$autofill}; <tr> <td></td> <td class='mw-input'> - <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" . $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " /> + <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" . + $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " /> </td> </tr> <tr> <td></td> <td class='mw-input'>" ); - $wgOut->addWikiText( wfMsgForContent( 'edittools' ) ); + $wgOut->addHTML( '<div class="mw-editTools">' ); + $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) ); + $wgOut->addHTML( '</div>' ); $wgOut->addHTML( " </td> </tr>" . Xml::closeElement( 'table' ) . Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) . + Xml::hidden( 'wpForReUpload', $this->mForReUpload, array( 'id' => 'wpForReUpload' ) ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) ); @@ -1648,7 +1686,7 @@ wgUploadAutoFill = {$autofill}; } } - wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output" ); + wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output \n" ); return $output; } } @@ -1685,7 +1723,7 @@ wgUploadAutoFill = {$autofill}; * @access private */ function cleanupTempFile() { - if ( $this->mRemoveTempFile && file_exists( $this->mTempPath ) ) { + if ( $this->mRemoveTempFile && $this->mTempPath && file_exists( $this->mTempPath ) ) { wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" ); unlink( $this->mTempPath ); } diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 6a4da7a4..b065bdd6 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -45,7 +45,7 @@ class LoginForm { */ function LoginForm( &$request, $par = '' ) { global $wgLang, $wgAllowRealName, $wgEnableEmail; - global $wgAuth; + global $wgAuth, $wgRedirectOnLogin; $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] $this->mName = $request->getText( 'wpName' ); @@ -66,6 +66,10 @@ class LoginForm { $this->mLanguage = $request->getText( 'uselang' ); $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); + if ( $wgRedirectOnLogin ) { + $this->mReturnTo = $wgRedirectOnLogin; + } + if( $wgEnableEmail ) { $this->mEmail = $request->getText( 'wpEmail' ); } else { @@ -593,7 +597,12 @@ class LoginForm { */ function mailPassword() { global $wgUser, $wgOut, $wgAuth; - + + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return false; + } + if( !$wgAuth->allowPasswordChange() ) { $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); return; @@ -654,7 +663,7 @@ class LoginForm { * @private */ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { - global $wgServer, $wgScript, $wgUser; + global $wgServer, $wgScript, $wgUser, $wgNewPasswordExpiry; if ( '' == $u->getEmail() ) { return new WikiError( wfMsg( 'noemail', $u->getName() ) ); @@ -670,7 +679,8 @@ class LoginForm { $u->setNewpassword( $np, $throttle ); $u->saveSettings(); - $m = wfMsg( $emailText, $ip, $u->getName(), $np, $wgServer . $wgScript ); + $m = wfMsgExt( $emailText, array( 'parsemag' ), $ip, $u->getName(), $np, + $wgServer . $wgScript, round( $wgNewPasswordExpiry / 86400 ) ); $result = $u->sendMail( wfMsg( $emailTitle ), $m ); return $result; @@ -968,6 +978,8 @@ class LoginForm { * @return string */ function makeLanguageSelector() { + global $wgLang; + $msg = wfMsgForContent( 'loginlanguagelinks' ); if( $msg != '' && !wfEmptyMsg( 'loginlanguagelinks', $msg ) ) { $langs = explode( "\n", $msg ); @@ -979,7 +991,7 @@ class LoginForm { $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); } } - return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', implode( ' | ', $links ) ) : ''; + return count( $links ) > 0 ? wfMsgHtml( 'loginlanguagelabel', $wgLang->pipeList( $links ) ) : ''; } else { return ''; } diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index ce0097b2..90619109 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -96,11 +96,18 @@ class UserrightsPage extends SpecialPage { // save settings if( $wgRequest->getCheck( 'saveusergroups' ) ) { $reason = $wgRequest->getVal( 'user-reason' ); - if( $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mTarget ) ) { + $tok = $wgRequest->getVal( 'wpEditToken' ); + if( $wgUser->matchEditToken( $tok, $this->mTarget ) ) { $this->saveUserGroups( $this->mTarget, $reason ); + + global $wgOut; + + $url = $this->getSuccessURL(); + $wgOut->redirect( $url ); + return; } } } @@ -110,6 +117,10 @@ class UserrightsPage extends SpecialPage { $this->editUserGroupsForm( $this->mTarget ); } } + + function getSuccessURL() { + return $this->getTitle( $this->mTarget )->getFullURL(); + } /** * Save user groups changes in the database. @@ -231,9 +242,9 @@ class UserrightsPage extends SpecialPage { * @return mixed User, UserRightsProxy, or null */ function fetchUser( $username ) { - global $wgOut, $wgUser; + global $wgOut, $wgUser, $wgUserrightsInterwikiDelimiter; - $parts = explode( '@', $username ); + $parts = explode( $wgUserrightsInterwikiDelimiter, $username ); if( count( $parts ) < 2 ) { $name = trim( $username ); $database = ''; diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index 29f527f2..95e06f4b 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -50,7 +50,7 @@ class SpecialVersion extends SpecialPage { $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) . "__NOTOC__ This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''', - copyright (C) 2001-2008 Magnus Manske, Brion Vibber, Lee Daniel Crocker, + copyright (C) 2001-2009 Magnus Manske, Brion Vibber, Lee Daniel Crocker, Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason, Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor, Aaron Schulz and others. diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php index c2731fa9..4957531e 100644 --- a/includes/specials/SpecialWantedfiles.php +++ b/includes/specials/SpecialWantedfiles.php @@ -72,9 +72,26 @@ class WantedFilesPage extends QueryPage { $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) : $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) ); - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); - return wfSpecialList($plink, $nlinks); + return wfSpecialList( + $plink, + $this->makeWlhLink( $nt, $skin, $result ) + ); + } + + /** + * Make a "what links here" link for a given title + * + * @param Title $title Title to make the link for + * @param Skin $skin Skin to use + * @param object $result Result row + * @return string + */ + private function makeWlhLink( $title, $skin, $result ) { + global $wgLang; + $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); + $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->value ) ); + return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) ); } } diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php index 10133409..7307b335 100644 --- a/includes/specials/SpecialWantedpages.php +++ b/includes/specials/SpecialWantedpages.php @@ -31,8 +31,7 @@ class WantedPagesPage extends QueryPage { $dbr = wfGetDB( DB_SLAVE ); $pagelinks = $dbr->tableName( 'pagelinks' ); $page = $dbr->tableName( 'page' ); - return - "SELECT 'Wantedpages' AS type, + $sql = "SELECT 'Wantedpages' AS type, pl_namespace AS namespace, pl_title AS title, COUNT(*) AS value @@ -46,6 +45,9 @@ class WantedPagesPage extends QueryPage { AND pg2.page_namespace != 8 GROUP BY pl_namespace, pl_title HAVING COUNT(*) > $count"; + + wfRunHooks( 'WantedPages::getSQL', array( &$this, &$sql ) ); + return $sql; } /** @@ -83,7 +85,7 @@ class WantedPagesPage extends QueryPage { return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) ); } else { $tsafe = htmlspecialchars( $result->title ); - return "Invalid title in result set; {$tsafe}"; + return wfMsg( 'wantedpages-badtitle', $tsafe ); } } diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php index 43b5cf8f..7dd9a262 100644 --- a/includes/specials/SpecialWantedtemplates.php +++ b/includes/specials/SpecialWantedtemplates.php @@ -42,7 +42,7 @@ class WantedTemplatesPage extends QueryPage { FROM $templatelinks LEFT JOIN $page ON tl_title = page_title AND tl_namespace = page_namespace WHERE page_title IS NULL AND tl_namespace = ". NS_TEMPLATE ." - GROUP BY tl_title + GROUP BY tl_namespace, tl_title "; } @@ -73,8 +73,6 @@ class WantedTemplatesPage extends QueryPage { $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) : $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) ); - $nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'), - $wgLang->formatNum( $result->value ) ); return wfSpecialList( $plink, $this->makeWlhLink( $nt, $skin, $result ) diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index 61dd6b3e..b14577b5 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -54,7 +54,7 @@ function wfSpecialWatchlist( $par ) { /* bool */ 'hideBots' => (int)$wgUser->getBoolOption( 'watchlisthidebots' ), /* bool */ 'hideAnons' => (int)$wgUser->getBoolOption( 'watchlisthideanons' ), /* bool */ 'hideLiu' => (int)$wgUser->getBoolOption( 'watchlisthideliu' ), - /* bool */ 'hidePatrolled' => (int)$wgUser->getBoolOption( 'watchlisthidepatrolled' ), // TODO + /* bool */ 'hidePatrolled' => (int)$wgUser->getBoolOption( 'watchlisthidepatrolled' ), /* bool */ 'hideOwn' => (int)$wgUser->getBoolOption( 'watchlisthideown' ), /* ? */ 'namespace' => 'all', /* ? */ 'invert' => false, @@ -96,7 +96,7 @@ function wfSpecialWatchlist( $par ) { } $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); - list( $page, $watchlist, $recentchanges ) = $dbr->tableNamesN( 'page', 'watchlist', 'recentchanges' ); + $recentchanges = $dbr->tableName( 'recentchanges' ); $watchlistCount = $dbr->selectField( 'watchlist', 'COUNT(*)', array( 'wl_user' => $uid ), __METHOD__ ); @@ -159,10 +159,12 @@ function wfSpecialWatchlist( $par ) { if( $wgUser->getOption( 'extendwatchlist' )) { $andLatest=''; $limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) ); + $usePage = false; } else { # Top log Ids for a page are not stored $andLatest = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; $limitWatchlist = 0; + $usePage = true; } # Show a message about slave lag, if applicable @@ -189,12 +191,11 @@ function wfSpecialWatchlist( $par ) { } $form .= '<hr />'; - $tables = array( 'recentchanges', 'watchlist', 'page' ); + $tables = array( 'recentchanges', 'watchlist' ); $fields = array( "{$recentchanges}.*" ); $conds = array(); $join_conds = array( 'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"), - 'page' => array('LEFT JOIN','rc_cur_id=page_id') ); $options = array( 'ORDER BY' => 'rc_timestamp DESC' ); if( $wgShowUpdatedMarker ) { @@ -212,7 +213,16 @@ function wfSpecialWatchlist( $par ) { if( $andHideAnons ) $conds[] = $andHideAnons; if( $andHidePatrolled ) $conds[] = $andHidePatrolled; if( $nameSpaceClause ) $conds[] = $nameSpaceClause; - + + $rollbacker = $wgUser->isAllowed('rollback'); + if ( $usePage || $rollbacker ) { + $tables[] = 'page'; + $join_conds['page'] = array('LEFT JOIN','rc_cur_id=page_id'); + if ($rollbacker) + $fields[] = 'page_latest'; + } + + ChangeTags::modifyDisplayQuery( $tables, $fields, $conds, $join_conds, $options, '' ); wfRunHooks('SpecialWatchlistQuery', array(&$conds,&$tables,&$join_conds,&$fields) ); $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $join_conds ); @@ -279,7 +289,7 @@ function wfSpecialWatchlist( $par ) { # Namespace filter and put the whole form together. $form .= $wlInfo; $form .= $cutofflinks; - $form .= implode( ' | ', $links ); + $form .= $wgLang->pipeList( $links ); $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); $form .= '<hr /><p>'; $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; @@ -317,6 +327,8 @@ function wfSpecialWatchlist( $par ) { $linkBatch->add( NS_USER, $userNameUnderscored ); } $linkBatch->add( NS_USER_TALK, $userNameUnderscored ); + + $linkBatch->add( $row->rc_namespace, $row->rc_title ); } $linkBatch->execute(); $dbr->dataSeek( $res, 0 ); @@ -348,7 +360,7 @@ function wfSpecialWatchlist( $par ) { $rc->numberofWatchingusers = 0; } - $s .= $list->recentChangesLine( $rc, $updated ); + $s .= $list->recentChangesLine( $rc, $updated, $counter ); } $s .= $list->endRecentChangesList(); @@ -380,6 +392,8 @@ function wlDaysLink( $d, $page, $options = array() ) { * Returns html */ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { + global $wgLang; + $hours = array( 1, 2, 6, 12 ); $days = array( 1, 3, 7 ); $i = 0; @@ -392,8 +406,8 @@ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { } return wfMsgExt('wlshowlast', array('parseinline', 'replaceafter'), - implode(' | ', $hours), - implode(' | ', $days), + $wgLang->pipeList( $hours ), + $wgLang->pipeList( $days ), wlDaysLink( 0, $page, $options ) ); } diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index d91b4960..3f485bd8 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -340,7 +340,7 @@ class WhatLinksHerePage { $limitLinks[] = $this->makeSelfLink( $prettyLimit, wfArrayToCGI( $overrides, $changed ) ); } - $nums = implode ( ' | ', $limitLinks ); + $nums = $wgLang->pipeList( $limitLinks ); return wfMsgHtml( 'viewprevnext', $prev, $next, $nums ); } @@ -389,6 +389,7 @@ class WhatLinksHerePage { } function getFilterPanel() { + global $wgLang; $show = wfMsgHtml( 'show' ); $hide = wfMsgHtml( 'hide' ); @@ -405,6 +406,6 @@ class WhatLinksHerePage { $overrides = array( $type => !$chosen ); $links[] = $this->makeSelfLink( $msg, wfArrayToCGI( $overrides, $changed ) ); } - return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), implode( ' | ', $links ) ); + return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), $wgLang->pipeList( $links ) ); } } diff --git a/includes/templates/NoLocalSettings.php b/includes/templates/NoLocalSettings.php index 5f7e93c7..42682d60 100644 --- a/includes/templates/NoLocalSettings.php +++ b/includes/templates/NoLocalSettings.php @@ -4,10 +4,7 @@ * @ingroup Templates */ -# Prevent XSS -if ( isset( $wgVersion ) ) { - $wgVersion = htmlspecialchars( $wgVersion ); -} else { +if ( !isset( $wgVersion ) ) { $wgVersion = 'VERSION'; } @@ -40,7 +37,7 @@ foreach( $topdirs as $dir ){ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns='http://www.w3.org/1999/xhtml' xml:lang='en' lang='en'> <head> - <title>MediaWiki <?php echo $wgVersion ?></title> + <title>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></title> <meta http-equiv='Content-Type' content='text/html; charset=utf-8' /> <style type='text/css' media='screen, projection'> html, body { @@ -56,15 +53,15 @@ foreach( $topdirs as $dir ){ </style> </head> <body> - <img src="<?php echo $path ?>skins/common/images/mediawiki.png" alt='The MediaWiki logo' /> + <img src="<?php echo htmlspecialchars( $path ) ?>skins/common/images/mediawiki.png" alt='The MediaWiki logo' /> - <h1>MediaWiki <?php echo $wgVersion ?></h1> + <h1>MediaWiki <?php echo htmlspecialchars( $wgVersion ) ?></h1> <div class='error'> <?php if ( file_exists( 'config/LocalSettings.php' ) ) { echo( 'To complete the installation, move <tt>config/LocalSettings.php</tt> to the parent directory.' ); } else { - echo( "Please <a href=\"${path}config/index.{$ext}\" title='setup'> set up the wiki</a> first." ); + echo( "Please <a href=\"" . htmlspecialchars( $path ) . "config/index." . htmlspecialchars( $ext ) . "\" title='setup'> set up the wiki</a> first." ); } ?> diff --git a/includes/templates/Userlogin.php b/includes/templates/Userlogin.php index c4a60b6c..1caa7ea2 100644 --- a/includes/templates/Userlogin.php +++ b/includes/templates/Userlogin.php @@ -242,7 +242,7 @@ class UsercreateTemplate extends QuickTemplate { if ( $inputItem['type'] == 'checkbox' && !empty( $inputItem['msg'] ) ) { ?> <label for="<?php echo htmlspecialchars( $inputItem['name'] ); ?>"><?php - $this->msg( $inputItem['msg'] ) ?></label><?php + $this->msgHtml( $inputItem['msg'] ) ?></label><?php } ?> </td> diff --git a/includes/zhtable/Makefile b/includes/zhtable/Makefile index 1407c93c..618e2f21 100644 --- a/includes/zhtable/Makefile +++ b/includes/zhtable/Makefile @@ -12,7 +12,7 @@ DIFF = LANG=zh_CN.UTF8 diff CC ?= gcc SF_MIRROR = easynews -SCIM_TABLES_VER = 0.5.8 +SCIM_TABLES_VER = 0.5.9 SCIM_PINYIN_VER = 0.5.91 LIBTABE_VER = 0.2.3 diff --git a/includes/zhtable/Makefile.py b/includes/zhtable/Makefile.py new file mode 100644 index 00000000..19436457 --- /dev/null +++ b/includes/zhtable/Makefile.py @@ -0,0 +1,438 @@ +# @author Philip +# You should run this script UNDER python 3000. +import tarfile, zipfile +import os, re, shutil, urllib.request + +# DEFINE +SF_MIRROR = 'easynews' +SCIM_TABLES_VER = '0.5.9' +SCIM_PINYIN_VER = '0.5.91' +LIBTABE_VER = '0.2.3' +# END OF DEFINE + +def GetFileFromURL( url, dest ): + if os.path.isfile(dest): + print( 'File %s up to date.' % dest ) + return + print( 'Downloading from [%s] ...' % url ) + urllib.request.urlretrieve( url, dest ) + print( 'Download complete.\n' ) + return + +def GetFileFromZip( path ): + print( 'Extracting files from %s ...' % path ) + zipfile.ZipFile(path).extractall() + return + +def GetFileFromTar( path, member, rename ): + print( 'Extracting %s from %s ...' % (rename, path) ) + tarfile.open(path, 'r:gz').extract(member) + shutil.move(member, rename) + tree_rmv = member.split('/')[0] + shutil.rmtree(tree_rmv) + return + +def ReadBIG5File( dest ): + print( 'Reading and decoding %s ...' % dest ) + f1 = open( dest, 'r', encoding='big5hkscs', errors='replace' ) + text = f1.read() + text = text.replace( '\ufffd', '\n' ) + f1.close() + f2 = open( dest, 'w', encoding='utf8' ) + f2.write(text) + f2.close() + return text + +def ReadFile( dest ): + print( 'Reading and decoding %s ...' % dest ) + f = open( dest, 'r', encoding='utf8' ) + ret = f.read() + f.close() + return ret + +def ReadUnihanFile( dest ): + print( 'Reading and decoding %s ...' % dest ) + f = open( dest, 'r', encoding='utf8' ) + t2s_code = [] + s2t_code = [] + while True: + line = f.readline() + if line: + if line.startswith('#'): + continue + elif not line.find('kSimplifiedVariant') == -1: + temp = line.split('kSimplifiedVariant') + t2s_code.append( ( temp[0].strip(), temp[1].strip() ) ) + elif not line.find('kTraditionalVariant') == -1: + temp = line.split('kTraditionalVariant') + s2t_code.append( ( temp[0].strip(), temp[1].strip() ) ) + else: + break + f.close() + return ( t2s_code, s2t_code ) + +def RemoveRows( text, num ): + text = re.sub( '.*\s*', '', text, num) + return text + +def RemoveOneCharConv( text ): + preg = re.compile('^.\s*$', re.MULTILINE) + text = preg.sub( '', text ) + return text + +def ConvertToChar( code ): + code = code.split('<')[0] + return chr( int( code[2:], 16 ) ) + +def GetDefaultTable( code_table ): + char_table = {} + for ( f, t ) in code_table: + if f and t: + from_char = ConvertToChar( f ) + to_chars = [ConvertToChar( code ) for code in t.split()] + char_table[from_char] = to_chars + return char_table + +def GetManualTable( dest ): + text = ReadFile( dest ) + temp1 = text.split() + char_table = {} + for elem in temp1: + elem = elem.strip('|') + if elem: + temp2 = elem.split( '|', 1 ) + from_char = chr( int( temp2[0][2:7], 16 ) ) + to_chars = [chr( int( code[2:7], 16 ) ) for code in temp2[1].split('|')] + char_table[from_char] = to_chars + return char_table + +def GetValidTable( src_table ): + valid_table = {} + for f, t in src_table.items(): + valid_table[f] = t[0] + return valid_table + +def GetToManyRules( src_table ): + tomany_table = {} + for f, t in src_table.items(): + for i in range(1, len(t)): + tomany_table[t[i]] = True + return tomany_table + +def RemoveRules( dest, table ): + text = ReadFile( dest ) + temp1 = text.split() + for elem in temp1: + f = '' + t = '' + elem = elem.strip().replace( '"', '' ).replace( '\'', '' ) + if '=>' in elem: + if elem.startswith( '=>' ): + t = elem.replace( '=>', '' ).strip() + elif elem.endswith( '=>' ): + f = elem.replace( '=>', '' ).strip() + else: + temp2 = elem.split( '=>' ) + f = temp2[0].strip() + t = temp2[1].strip() + try: + table.pop(f, t) + continue + except: + continue + else: + f = t = elem + if f: + try: + table.pop(f) + except: + x = 1 + if t: + for temp_f, temp_t in table.copy().items(): + if temp_t == t: + table.pop(temp_f) + return table + +def DictToSortedList1( src_table ): + return sorted( src_table.items(), key = lambda m: m[0] ) #sorted( temp_table, key = lambda m: len( m[0] ) ) + +def DictToSortedList2( src_table ): + return sorted( src_table.items(), key = lambda m: m[1] ) + +def Converter( string, conv_table ): + i = 0 + while i < len(string): + for j in range(len(string) - i, 0, -1): + f = string[i:][:j] + t = conv_table.get( f ) + if t: + string = string[:i] + t + string[i:][j:] + i += len(t) - 1 + break + i += 1 + return string + +def GetDefaultWordsTable( src_wordlist, src_tomany, char_conv_table, char_reconv_table ): + wordlist = list( set( src_wordlist ) ) + wordlist.sort( key = len, reverse = True ) + word_conv_table = {} + word_reconv_table = {} + while wordlist: + conv_table = {} + reconv_table = {} + conv_table.update( word_conv_table ) + conv_table.update( char_conv_table ) + reconv_table.update( word_reconv_table ) + reconv_table.update( char_reconv_table ) + word = wordlist.pop() + new_word_len = word_len = len(word) + while new_word_len == word_len: + rvt_test = False + for char in word: + rvt_test = rvt_test or src_tomany.get(char) + test_word = Converter( word, reconv_table ) + new_word = Converter( word, conv_table ) + if not reconv_table.get( new_word ): + if not test_word == word: + word_conv_table[word] = new_word + word_reconv_table[new_word] = word + elif rvt_test: + rvt_word = Converter( new_word, reconv_table ) + if not rvt_word == word: + word_conv_table[word] = new_word + word_reconv_table[new_word] = word + try: + word = wordlist.pop() + except IndexError: + break + new_word_len = len(word) + return word_reconv_table + +def GetManualWordsTable( src_wordlist, conv_table ): + src_wordlist = [items.split('#')[0].strip() for items in src_wordlist] + wordlist = list( set( src_wordlist ) ) + wordlist.sort( key = len, reverse = True ) + reconv_table = {} + while wordlist: + word = wordlist.pop() + new_word = Converter( word, conv_table ) + reconv_table[new_word] = word + return reconv_table + +def CustomRules( dest ): + text = ReadFile( dest ) + temp = text.split() + ret = {temp[i]: temp[i + 1] for i in range( 0, len( temp ), 2 )} + return ret + +def GetPHPArray( table ): + lines = ['\'%s\' => \'%s\',' % (f, t) for (f, t) in table] + #lines = ['"%s"=>"%s",' % (f, t) for (f, t) in table] + return '\n'.join(lines) + +def RemoveSameChar( src_table ): + dst_table = {} + for f, t in src_table.items(): + if not f == t: + dst_table[f] = t + return dst_table + +def main(): + #Get Unihan.zip: + url = 'ftp://ftp.unicode.org/Public/UNIDATA/Unihan.zip' + han_dest = 'Unihan.zip' + GetFileFromURL( url, han_dest ) + + # Get scim-tables-$(SCIM_TABLES_VER).tar.gz: + url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-tables-%s.tar.gz' % ( SF_MIRROR, SCIM_TABLES_VER ) + tbe_dest = 'scim-tables-%s.tar.gz' % SCIM_TABLES_VER + GetFileFromURL( url, tbe_dest ) + + # Get scim-pinyin-$(SCIM_PINYIN_VER).tar.gz: + url = 'http://%s.dl.sourceforge.net/sourceforge/scim/scim-pinyin-%s.tar.gz' % ( SF_MIRROR, SCIM_PINYIN_VER ) + pyn_dest = 'scim-pinyin-%s.tar.gz' % SCIM_PINYIN_VER + GetFileFromURL( url, pyn_dest ) + + # Get libtabe-$(LIBTABE_VER).tgz: + url = 'http://%s.dl.sourceforge.net/sourceforge/libtabe/libtabe-%s.tgz' % ( SF_MIRROR, LIBTABE_VER ) + lbt_dest = 'libtabe-%s.tgz' % LIBTABE_VER + GetFileFromURL( url, lbt_dest ) + + # Extract the file from a comressed files + + # Unihan.txt Simp. & Trad + GetFileFromZip( han_dest ) + + # Make word lists + t_wordlist = [] + s_wordlist = [] + + # EZ.txt.in Trad + src = 'scim-tables-%s/tables/zh/EZ-Big.txt.in' % SCIM_TABLES_VER + dst = 'EZ.txt.in' + GetFileFromTar( tbe_dest, src, dst ) + text = ReadFile( dst ) + text = text.split( 'BEGIN_TABLE' )[1].strip() + text = text.split( 'END_TABLE' )[0].strip() + text = re.sub( '.*\t', '', text ) + text = RemoveOneCharConv( text ) + t_wordlist.extend( text.split() ) + + # Wubi.txt.in Simp + src = 'scim-tables-%s/tables/zh/Wubi.txt.in' % SCIM_TABLES_VER + dst = 'Wubi.txt.in' + GetFileFromTar( tbe_dest, src, dst ) + text = ReadFile( dst ) + text = text.split( 'BEGIN_TABLE' )[1].strip() + text = text.split( 'END_TABLE' )[0].strip() + text = re.sub( '.*\t(.*?)\t\d*', '\g<1>', text ) + text = RemoveOneCharConv( text ) + s_wordlist.extend( text.split() ) + + # Ziranma.txt.in Simp + src = 'scim-tables-%s/tables/zh/Ziranma.txt.in' % SCIM_TABLES_VER + dst = 'Ziranma.txt.in' + GetFileFromTar( tbe_dest, src, dst ) + text = ReadFile( dst ) + text = text.split( 'BEGIN_TABLE' )[1].strip() + text = text.split( 'END_TABLE' )[0].strip() + text = re.sub( '.*\t(.*?)\t\d*', '\g<1>', text ) + text = RemoveOneCharConv( text ) + s_wordlist.extend( text.split() ) + + # phrase_lib.txt Simp + src = 'scim-pinyin-%s/data/phrase_lib.txt' % SCIM_PINYIN_VER + dst = 'phrase_lib.txt' + GetFileFromTar( pyn_dest, src, dst ) + text = ReadFile( 'phrase_lib.txt' ) + text = re.sub( '(.*)\t\d\d*.*', '\g<1>', text) + text = RemoveRows( text, 5 ) + text = RemoveOneCharConv( text ) + s_wordlist.extend( text.split() ) + + # tsi.src Trad + src = 'libtabe/tsi-src/tsi.src' + dst = 'tsi.src' + GetFileFromTar( lbt_dest, src, dst ) + text = ReadBIG5File( 'tsi.src' ) + text = re.sub( ' \d.*', '', text.replace('# ', '')) + text = RemoveOneCharConv( text ) + t_wordlist.extend( text.split() ) + + # remove duplicate elements + t_wordlist = list( set( t_wordlist ) ) + s_wordlist = list( set( s_wordlist ) ) + + # simpphrases_exclude.manual Simp + text = ReadFile( 'simpphrases_exclude.manual' ) + temp = text.split() + s_string = '\n'.join( s_wordlist ) + for elem in temp: + s_string = re.sub( '.*%s.*\n' % elem, '', s_string ) + s_wordlist = s_string.split('\n') + + # tradphrases_exclude.manual Trad + text = ReadFile( 'tradphrases_exclude.manual' ) + temp = text.split() + t_string = '\n'.join( t_wordlist ) + for elem in temp: + t_string = re.sub( '.*%s.*\n' % elem, '', t_string ) + t_wordlist = t_string.split('\n') + + # Make char to char convertion table + # Unihan.txt, dict t2s_code, s2t_code = { 'U+XXXX': 'U+YYYY( U+ZZZZ) ... ', ... } + ( t2s_code, s2t_code ) = ReadUnihanFile( 'Unihan.txt' ) + # dict t2s_1tomany = { '\uXXXX': '\uYYYY\uZZZZ ... ', ... } + t2s_1tomany = {} + t2s_1tomany.update( GetDefaultTable( t2s_code ) ) + t2s_1tomany.update( GetManualTable( 'trad2simp.manual' ) ) + # dict s2t_1tomany + s2t_1tomany = {} + s2t_1tomany.update( GetDefaultTable( s2t_code ) ) + s2t_1tomany.update( GetManualTable( 'simp2trad.manual' ) ) + # dict t2s_1to1 = { '\uXXXX': '\uYYYY', ... }; t2s_trans = { 'ddddd': '', ... } + t2s_1to1 = GetValidTable( t2s_1tomany ) + s_tomany = GetToManyRules( t2s_1tomany ) + # dict s2t_1to1; s2t_trans + s2t_1to1 = GetValidTable( s2t_1tomany ) + t_tomany = GetToManyRules( s2t_1tomany ) + # remove noconvert rules + t2s_1to1 = RemoveRules( 'trad2simp_noconvert.manual', t2s_1to1 ) + s2t_1to1 = RemoveRules( 'simp2trad_noconvert.manual', s2t_1to1 ) + + # Make word to word convertion table + t2s_1to1_supp = t2s_1to1.copy() + s2t_1to1_supp = s2t_1to1.copy() + # trad2simp_supp_set.manual + t2s_1to1_supp.update( CustomRules( 'trad2simp_supp_set.manual' ) ) + # simp2trad_supp_set.manual + s2t_1to1_supp.update( CustomRules( 'simp2trad_supp_set.manual' ) ) + # simpphrases.manual + text = ReadFile( 'simpphrases.manual' ) + s_wordlist_manual = text.split('\n') + t2s_word2word_manual = GetManualWordsTable(s_wordlist_manual, s2t_1to1_supp) + t2s_word2word_manual.update( CustomRules( 'toSimp.manual' ) ) + # tradphrases.manual + text = ReadFile( 'tradphrases.manual' ) + t_wordlist_manual = text.split('\n') + s2t_word2word_manual = GetManualWordsTable(t_wordlist_manual, t2s_1to1_supp) + s2t_word2word_manual.update( CustomRules( 'toTrad.manual' ) ) + # t2s_word2word + s2t_supp = s2t_1to1_supp.copy() + s2t_supp.update( s2t_word2word_manual ) + t2s_supp = t2s_1to1_supp.copy() + t2s_supp.update( t2s_word2word_manual ) + t2s_word2word = GetDefaultWordsTable( s_wordlist, s_tomany, s2t_1to1_supp, t2s_supp ) + ## toSimp.manual + t2s_word2word.update( t2s_word2word_manual ) + # s2t_word2word + s2t_word2word = GetDefaultWordsTable( t_wordlist, t_tomany, t2s_1to1_supp, s2t_supp ) + ## toTrad.manual + s2t_word2word.update( s2t_word2word_manual ) + + # Final tables + # sorted list toHans + t2s_1to1 = RemoveSameChar( t2s_1to1 ) + s2t_1to1 = RemoveSameChar( s2t_1to1 ) + toHans = DictToSortedList1( t2s_1to1 ) + DictToSortedList2( t2s_word2word ) + # sorted list toHant + toHant = DictToSortedList1( s2t_1to1 ) + DictToSortedList2( s2t_word2word ) + # sorted list toCN + toCN = DictToSortedList2( CustomRules( 'toCN.manual' ) ) + # sorted list toHK + toHK = DictToSortedList2( CustomRules( 'toHK.manual' ) ) + # sorted list toSG + toSG = DictToSortedList2( CustomRules( 'toSG.manual' ) ) + # sorted list toTW + toTW = DictToSortedList2( CustomRules( 'toTW.manual' ) ) + + # Get PHP Array + php = '''<?php +/** + * Simplified / Traditional Chinese conversion tables + * + * Automatically generated using code and data in includes/zhtable/ + * Do not modify directly! + */ + +$zh2Hant = array(\n''' + php += GetPHPArray( toHant ) + php += '\n);\n\n$zh2Hans = array(\n' + php += GetPHPArray( toHans ) + php += '\n);\n\n$zh2TW = array(\n' + php += GetPHPArray( toTW ) + php += '\n);\n\n$zh2HK = array(\n' + php += GetPHPArray( toHK ) + php += '\n);\n\n$zh2CN = array(\n' + php += GetPHPArray( toCN ) + php += '\n);\n\n$zh2SG = array(\n' + php += GetPHPArray( toSG ) + php += '\n);' + + f = open( 'ZhConversion.php', 'w', encoding = 'utf8' ) + print ('Writing ZhConversion.php ... ') + f.write( php ) + f.close() + +if __name__ == '__main__': + main()
\ No newline at end of file diff --git a/includes/zhtable/simp2trad.manual b/includes/zhtable/simp2trad.manual index 140495f9..2a405073 100644 --- a/includes/zhtable/simp2trad.manual +++ b/includes/zhtable/simp2trad.manual @@ -202,3 +202,160 @@ U+06881梁|U+06881梁|U+06a11樑| U+05e84庄|U+05e84庄|U+0838a莊| U+062fc拼|U+062fc拼|U+062da拚| U+08d5e赞|U+08d0a贊|U+08b9a讚| +U+0E82D|U+068E1棡| +U+29F8C𩾌|U+09C47鱇| +U+070BC炼|U+07149煉|U+0934A鍊| +U+04E2A个|U+0500B個|U+07B87箇| +U+094F2铲|U+093DF鏟|U+05277剷| +U+05EB5庵|U+05EB5庵|U+083F4菴| +U+05F69彩|U+05F69彩|U+07DB5綵| +U+20BB6𠮶|U+055F0嗰| +U+0497A䥺|U+091FE釾| +U+0497D䥽|U+093FA鏺| +U+04983䦃|U+0942F鐯| +U+04985䦅|U+09425鐥| +U+04B6A䭪|U+297AF𩞯| +U+04C9F䲟|U+09BA3鮣| +U+04CA0䲠|U+09C06鰆| +U+04CA1䲡|U+09C0C鰌| +U+04CA2䲢|U+09C27鰧| +U+04CA3䲣|U+04C77䱷| +U+28C3E𨰾|U+093B7鎷| +U+28C3F𨰿|U+091F3釳| +U+28C40𨱀|U+2895B𨥛| +U+28C41𨱁|U+09220鈠| +U+28C42𨱂|U+0920B鈋| +U+28C43𨱃|U+09232鈲| +U+28C44𨱄|U+0922F鈯| +U+28C45𨱅|U+09241鉁| +U+28C47𨱇|U+092B6銶| +U+28C48𨱈|U+092C9鋉| +U+28C49𨱉|U+09344鍄| +U+28C4A𨱊|U+289F1𨧱| +U+28C4B𨱋|U+09302錂| +U+28C4C𨱌|U+093C6鏆| +U+28C4D𨱍|U+093AF鎯| +U+28C4E𨱎|U+0936E鍮| +U+28C4F𨱏|U+0939D鎝| +U+28C50𨱐|U+28AD2𨫒| +U+28C52𨱒|U+093C9鏉| +U+28C53𨱓|U+0940E鐎| +U+28C54𨱔|U+0940F鐏| +U+28C55𨱕|U+28B82𨮂| +U+28E02𨸂|U+0958D閍| +U+28E03𨸃|U+09590閐| +U+293FC𩏼|U+04A8F䪏| +U+293FD𩏽|U+293EA𩏪| +U+293FE𩏾|U+293A2𩎢| +U+293FF𩏿|U+04A98䪘| +U+29400𩐀|U+04A97䪗| +U+29595𩖕|U+294E3𩓣| +U+29596𩖖|U+09843顃| +U+29597𩖗|U+04AF4䫴| +U+29665𩙥|U+098B0颰| +U+29666𩙦|U+295C0𩗀| +U+29667𩙧|U+295E1𩗡| +U+29668𩙨|U+29639𩘹| +U+29669𩙩|U+29600𩘀| +U+2966A𩙪|U+098B7颷| +U+2966B𩙫|U+098BE颾| +U+2966C𩙬|U+2963A𩘺| +U+2966D𩙭|U+2961D𩘝| +U+2966E𩙮|U+04B18䬘| +U+2966F𩙯|U+04B1D䬝| +U+29670𩙰|U+29648𩙈| +U+29805𩠅|U+297D0𩟐| +U+29806𩠆|U+29726𩜦| +U+29807𩠇|U+04B40䭀| +U+29808𩠈|U+04B43䭃| +U+2980B𩠋|U+29754𩝔| +U+2980C𩠌|U+09938餸| +U+299E6𩧦|U+2987A𩡺| +U+299E8𩧨|U+099CE駎| +U+299E9𩧩|U+2990A𩤊| +U+299EA𩧪|U+04BBE䮾| +U+299EB𩧫|U+099DA駚| +U+299EC𩧬|U+298A1𩢡| +U+299ED𩧭|U+04B7F䭿| +U+299EE𩧮|U+298BE𩢾| +U+299EF𩧯|U+09A4B驋| +U+299F0𩧰|U+04B9D䮝| +U+299F1𩧱|U+29949𩥉| +U+299F2𩧲|U+099E7駧| +U+299F3𩧳|U+298B8𩢸| +U+299F4𩧴|U+099E9駩| +U+299F5𩧵|U+298B4𩢴| +U+299F6𩧶|U+298CF𩣏| +U+299FA𩧺|U+099F6駶| +U+299FB𩧻|U+298F5𩣵| +U+299FC𩧼|U+298FA𩣺| +U+299FF𩧿|U+04BA0䮠| +U+29A00𩨀|U+09A14騔| +U+29A01𩨁|U+04B9E䮞| +U+29A03𩨃|U+09A1D騝| +U+29A04𩨄|U+09A2A騪| +U+29A05𩨅|U+29938𩤸| +U+29A06𩨆|U+29919𩤙| +U+29A08𩨈|U+09A1F騟| +U+29A09𩨉|U+29932𩤲| +U+29A0A𩨊|U+09A1A騚| +U+29A0B𩨋|U+29944𩥄| +U+29A0C𩨌|U+29951𩥑| +U+29A0D𩨍|U+29947𩥇| +U+29A0F𩨏|U+04BB3䮳| +U+29A10𩨐|U+299C6𩧆| +U+29F79𩽹|U+09B65魥| +U+29F7A𩽺|U+29D69𩵩| +U+29F7B𩽻|U+29D79𩵹| +U+29F7C𩽼|U+09BF6鯶| +U+29F7D𩽽|U+29DB1𩶱| +U+29F7E𩽾|U+09B9F鮟| +U+29F7F𩽿|U+29DB0𩶰| +U+29F80𩾀|U+09B95鮕| +U+29F81𩾁|U+09BC4鯄| +U+29F83𩾃|U+09BB8鮸| +U+29F84𩾄|U+29DF0𩷰| +U+29F85𩾅|U+29E03𩸃| +U+29F86𩾆|U+29E26𩸦| +U+29F87𩾇|U+09BF1鯱| +U+29F88𩾈|U+04C59䱙| +U+29F8A𩾊|U+04C6C䱬| +U+29F8B𩾋|U+04C70䱰| +U+29F8C𩾌|U+09C47鱇| +U+29F8E𩾎|U+29F47𩽇| +U+2A242𪉂|U+04CB0䲰| +U+2A243𪉃|U+09CFC鳼| +U+2A244𪉄|U+29FEA𩿪| +U+2A245𪉅|U+2A026𪀦| +U+2A246𪉆|U+09D32鴲| +U+2A248𪉈|U+09D1C鴜| +U+2A249𪉉|U+2A048𪁈| +U+2A24A𪉊|U+09DE8鷨| +U+2A24B𪉋|U+2A03E𪀾| +U+2A24C𪉌|U+2A056𪁖| +U+2A24D𪉍|U+09D5A鵚| +U+2A24E𪉎|U+2A086𪂆| +U+2A24F𪉏|U+2A0CF𪃏| +U+2A250𪉐|U+2A0CD𪃍| +U+2A251𪉑|U+09DD4鷔| +U+2A252𪉒|U+2A115𪄕| +U+2A254𪉔|U+2A106𪄆| +U+2A255𪉕|U+2A1F3𪇳| +U+2A388𪎈|U+04D2C䴬| +U+2A389𪎉|U+09EB2麲| +U+2A38A𪎊|U+09EA8麨| +U+2A38B𪎋|U+04D34䴴| +U+2A38C𪎌|U+09EB3麳| +U+2A68F𪚏|U+2A600𪘀| +U+2A690𪚐|U+2A62F𪘯| +U+0621A戚|U+0621A戚|U+0617C慼|U+093DA鏚| +U+057FC埼|U+057FC埼|U+07895碕| +U+065D7旗|U+065D7旗|U+065C2旂| +U+06328挨|U+06328挨|U+06371捱| +U+06EAF溯|U+06EAF溯|U+06CDD泝| +U+09178酸|U+09178酸|U+075E0痠| +U+07A57穗|U+07A57穗|U+07E50繐| +U+084D1蓑|U+084D1蓑|U+07C11簑| +U+26216𦈖|U+04308䌈| +U+03CE0㳠|U+06FBE澾| +U+0447D䑽|U+26A99𦪙|
\ No newline at end of file diff --git a/includes/zhtable/simp2trad_supp_set.manual b/includes/zhtable/simp2trad_supp_set.manual index 0f479906..a5038a5d 100644 --- a/includes/zhtable/simp2trad_supp_set.manual +++ b/includes/zhtable/simp2trad_supp_set.manual @@ -1,2 +1,2 @@ 余 餘 -着 著 +着 著
\ No newline at end of file diff --git a/includes/zhtable/simpphrases.manual b/includes/zhtable/simpphrases.manual index 60d0861c..6339ff83 100644 --- a/includes/zhtable/simpphrases.manual +++ b/includes/zhtable/simpphrases.manual @@ -1,383 +1,2155 @@ -乾坤 -乾隆 -旋乾转坤 -乾陵 +乾上乾下 +乾为天 +乾为阳 +乾九 +乾乾 +乾亨 +乾仪 +乾位 +乾健 +乾元 +乾光 +乾兴 +乾冈 +乾刘 +乾刚 +乾化 +乾卦 乾县 -康乾 +乾台 +乾吉 +乾启 +乾命 +乾和 乾嘉 -乾盛世 -郭子乾 -张法乾 -萧乾 -乾旦 -乾断 乾图 -乾纲 -乾红 -乾乾 +乾坤 +乾城 +乾基 +乾始 +乾姓 +乾宁 +乾宅 +乾宇 +乾安 +乾定 +乾封 +乾居 +乾岗 +乾巛 +乾州 +乾式 +乾录 +乾律 +乾德 +乾心 +乾文 +乾断 +乾方 +乾施 +乾旦 +乾明 +乾昧 +乾晖 +乾景 +乾晷 +乾曜 +乾构 +乾枢 +乾栋 +乾步 +乾氏 +乾泉 乾清宫 +乾渥 +乾灵 +乾男 +乾皋 +乾盛世 +乾矢 +乾祐 +乾穹 +乾窦 +乾竺 +乾笃 +乾符 +乾策 +乾精 +乾红 +乾纲 +乾纽 +乾络 +乾统 +乾维 +乾罗 +乾花 +乾荫 +乾行 +乾衡 +乾覆 乾象 -乾宅 +乾象历 +乾贞 +乾贶 +乾车 +乾轴 乾造 -乾曜 -乾元 -乾卦 +乾道 +乾鉴 +乾钧 +乾闼 +乾陀 +乾陵 +乾隆 +乾音 +乾顾 +乾风 +乾首 +乾马 +乾鹄 +乾鹊 +乾龙 +乾,健也 +乾,天也 +坤乾 +天道为乾 +尼乾陀 +康乾 +张法乾 +旋乾转坤 +易·乾 +《易乾 +《周易乾 +易经·乾 +易经乾 李乾德 -挨着 -爱着 -暗着 -昂着 -摆着 -伴着 -办着 -帮着 -绑着 -抱着 -背着 -备着 -本着 -本著作 -本著名 -本著者 -逼着 -闭着 -变着 -不着边际 +萧乾 +郭子乾 +雍乾 不着痕迹 -猜着 -踩着 -藏着 -侧着 -缠着 -敞着 -唱着 -朝着 -沉着 -乘着 -持着 -斥着 +不着边际 +与着 +与著书 +与著作 +与著名 +与著录 +与著称 +与著者 +与著述 丑着 -穿着 -吹着 -达着 -打着 -待着 -带着 -戴着 -当着 -挡着 -得着 -得著作 -得著者 -得著名 -瞪着 +丑著书 +丑著作 +丑著名 +丑著录 +丑著称 +丑著者 +丑著述 +临着 +临著书 +临著作 +临著名 +临著录 +临著称 +临著者 +临著述 +丽着 +丽著书 +丽著作 +丽著名 +丽著录 +丽著称 +丽著者 +丽著述 +乐着 +乐著书 +乐著作 +乐著名 +乐著录 +乐著称 +乐著者 +乐著述 +乘着 +乘著书 +乘著作 +乘著名 +乘著录 +乘著称 +乘著者 +乘著述 +争着 +争著书 +争著作 +争著名 +争著录 +争著称 +争著者 +争著述 +亮着 +亮著书 +亮著作 +亮著名 +亮著录 +亮著称 +亮著者 +亮著述 +仗着 +仗著书 +仗著作 +仗著名 +仗著录 +仗著称 +仗著者 +仗著述 +代表着 +代表著书 +代表著作 +代表著名 +代表著录 +代表著称 +代表著者 +代表著述 +伴着 +伴著书 +伴著作 +伴著名 +伴著录 +伴著称 +伴著者 +伴著述 低着 -点着 -盯着 -顶着 -定着 -定著作 -动着 -斗着 -独着 -对着 -对著名 -对著作 -对著者 -盾着 -犯不着 -福着 -赶着 -高着 -隔着 -跟着 -孤着 +低著书 +低著作 +低著名 +低著录 +低著称 +低著者 +低著述 +住着 +住著书 +住著作 +住著名 +住著录 +住著称 +住著者 +住著述 +侧着 +侧著书 +侧著作 +侧著名 +侧著录 +侧著称 +侧著者 +侧著述 +保障着 +保障著书 +保障著作 +保障著名 +保障著录 +保障著称 +保障著者 +保障著述 +信着 +信著书 +信著作 +信著名 +信著录 +信著称 +信著者 +信著述 +候着 +候著书 +候著作 +候著名 +候著录 +候著称 +候著者 +候著述 +借着 +借著书 +借著作 +借著名 +借著录 +借著称 +借著者 +借著述 +做着 +做著书 +做著作 +做著名 +做著录 +做著称 +做著者 +做著述 +偷着 +偷著书 +偷著作 +偷著名 +偷著录 +偷著称 +偷著者 +偷著述 +光着 +光著书 +光著作 +光著名 +光著录 +光著称 +光著者 +光著述 关着 +关著书 关著作 关著名 +关著录 +关著称 关著者 -管着 -惯着 -光着 -光著作 -跪着 -裹着 -撼着 -喝着 -候着 -怀着 -晃着 -挥着 -活着 -获着 -获着 -急着 -记着 +关著述 冀着 -夹着 -驾着 -见着 -闲着 -叫着 -接着 -借着 -借着 -据着 -据著作 -据著名 -据著者 -开着 -看着 -康着 -扛着 -考着 -渴着 +冀著书 +冀著作 +冀著名 +冀著录 +冀著称 +冀著者 +冀著述 +冒着 +冒著书 +冒著作 +冒著名 +冒著录 +冒著称 +冒著者 +冒著述 +写着 +写著书 +写著作 +写著名 +写著录 +写著称 +写著者 +写著述 +凉着 +凉著书 +凉著作 +凉著名 +凉著录 +凉著称 +凉著者 +凉著述 +制着 +制著书 +制著作 +制著名 +制著录 +制著称 +制著者 +制著述 刻着 -空着 -哭着 -苦着 -捆着 -困着 -拉着 -来着 -乐着 -乐著作 +刻著书 +刻著作 +刻著名 +刻著录 +刻著称 +刻著者 +刻著述 +办着 +办著书 +办著作 +办著名 +办著录 +办著称 +办著者 +办著述 +动着 +动著书 +动著作 +动著名 +动著录 +动著称 +动著者 +动著述 努力着 -丽着 -连着 -连著作 -连著名 -连著者 -恋着 -凉着 -亮着 -临着 -拎着 -领着 -流着 -留着 -搂着 -陋着 -落着 -骂着 -瞒着 -漫着 -忙着 -冒着 -美着 -美著作 -美著名 -美著者 -梦着 -蒙着 -拿着 -逆着 -酿着 +努力著书 +努力著作 +努力著名 +努力著录 +努力著称 +努力著者 +努力著述 努着 -趴着 -跑着 -陪着 -配着 -披着 -骗着 -飘着 -拼着 -铺着 -骑着 -牵着 -求着 +努著书 +努著作 +努著名 +努著录 +努著称 +努著者 +努著述 +印着 +印著书 +印著作 +印著名 +印著录 +印著称 +印著者 +印著述 +压着 +压著书 +压著作 +压著名 +压著录 +压著称 +压著者 +压著述 去着 -嚷着 -绕着 -忍着 -揉着 -润着 -烧着 -身着 -沉着 -盛着 -试着 -守着 +去著书 +去著作 +去著名 +去著录 +去著称 +去著者 +去著述 受着 +受著书 受著作 受著名 +受著录 +受著称 受著者 -梳着 -竖着 -数着 -睡不着 -睡着 -顺着 -随着 -踏着 -抬着 -躺着 -提着 -甜着 -挑着 -跳着 +受著述 +变着 +变著书 +变著作 +变著名 +变著录 +变著称 +变著者 +变著述 +叫着 +叫著书 +叫著作 +叫著名 +叫著录 +叫著称 +叫著者 +叫著述 +向着 +向著书 +向著作 +向著名 +向著录 +向著称 +向著者 +向著述 +含着 +含著书 +含著作 +含著名 +含著录 +含著称 +含著者 +含著述 听着 -听著名 +听著书 听著作 -听著者 -听着 听著名 -听著作 +听著录 +听著称 听著者 -偷着 -拖着 -望着 -围着 +听著述 +吹着 +吹著书 +吹著作 +吹著名 +吹著录 +吹著称 +吹著者 +吹著述 味着 -想着 +味著书 +味著作 +味著名 +味著录 +味著称 +味著者 +味著述 响着 -向着 -笑着 +响著书 +响著作 +响著名 +响著录 +响著称 +响著者 +响著述 +哭着 +哭著书 +哭著作 +哭著名 +哭著录 +哭著称 +哭著者 +哭著述 +唱着 +唱著书 +唱著作 +唱著名 +唱著录 +唱著称 +唱著者 +唱著述 +喝着 +喝著书 +喝著作 +喝著名 +喝著录 +喝著称 +喝著者 +喝著述 +嚷着 +嚷著书 +嚷著作 +嚷著名 +嚷著录 +嚷著称 +嚷著者 +嚷著述 +因着 +因著书 +因著作 +因著名 +因著录 +因著称 +因著者 +因著述 +困着 +困著书 +困著作 +困著名 +困著录 +困著称 +困著者 +困著述 +围着 +围著书 +围著作 +围著名 +围著录 +围著称 +围著者 +围著述 +在着 +在著书 +在著作 +在著名 +在著录 +在著称 +在著者 +在著述 +坐着 +坐著书 +坐著作 +坐著名 +坐著录 +坐著称 +坐著者 +坐著述 +备着 +备著书 +备著作 +备著名 +备著录 +备著称 +备著者 +备著述 +夹着 +夹著书 +夹著作 +夹著名 +夹著录 +夹著称 +夹著者 +夹著述 +孤着 +孤著书 +孤著作 +孤著名 +孤著录 +孤著称 +孤著者 +孤著述 +学着 +学著书 +学著作 +学著名 +学著录 +学著称 +学著者 +学著述 +守着 +守著书 +守著作 +守著名 +守著录 +守著称 +守著者 +守著述 +定着 +定著书 +定著作 +定著名 +定著录 +定著称 +定著者 +定著述 +对着 +对著书 +对著作 +对著名 +对著录 +对著称 +对著者 +对著述 +寻着 +寻著书 +寻著作 +寻著名 +寻著录 +寻著称 +寻著者 +寻著述 +展着 +展著书 +展著作 +展著名 +展著录 +展著称 +展著者 +展著述 +带着 +带著书 +带著作 +带著名 +带著录 +带著称 +带著者 +带著述 +帮着 +帮著书 +帮著作 +帮著名 +帮著录 +帮著称 +帮著者 +帮著述 +应着 +应著书 +应著作 +应著名 +应著录 +应著称 +应著者 +应著述 +康着 +康著书 +康著作 +康著名 +康著录 +康著称 +康著者 +康著述 +开着 +开著书 +开著作 +开著名 +开著录 +开著称 +开著者 +开著述 +当着 +当著书 +当著作 +当著名 +当著录 +当著称 +当著者 +当著述 +待着 +待著书 +待著作 +待著名 +待著录 +待著称 +待著者 +待著述 +得着 +得著书 +得著作 +得著名 +得著录 +得著称 +得著者 +得著述 +循着 +循著书 +循著作 +循著名 +循著录 +循著称 +循著者 +循著述 心着 -新著龙虎门 -信着 -行着 +心著书 +心著作 +心著名 +心著录 +心著称 +心著者 +心著述 +忍着 +忍著书 +忍著作 +忍著名 +忍著录 +忍著称 +忍著者 +忍著述 +志着 +志著书 +志著作 +志著名 +志著录 +志著称 +志著者 +志著述 +忙着 +忙著书 +忙著作 +忙著名 +忙著录 +忙著称 +忙著者 +忙著述 +怀着 +怀著书 +怀著作 +怀著名 +怀著录 +怀著称 +怀著者 +怀著述 +急着 +急著书 +急著作 +急著名 +急著录 +急著称 +急著者 +急著述 性着 +性著书 性著作 性著名 +性著录 +性著称 性著者 性著述 -学着 -学著作 -寻着 -循着 -压着 -雅着 -沿着 -耀着 +恋着 +恋著书 +恋著作 +恋著名 +恋著录 +恋著称 +恋著者 +恋著述 +悠着 +悠著书 +悠著作 +悠著名 +悠著录 +悠著称 +悠著者 +悠著述 +惯着 +惯著书 +惯著作 +惯著名 +惯著录 +惯著称 +惯著者 +惯著述 +想着 +想著书 +想著作 +想著名 +想著录 +想著称 +想著者 +想著述 +战着 +战著书 +战著作 +战著名 +战著录 +战著称 +战著者 +战著述 +戴着 +戴著书 +戴著作 +戴著名 +戴著录 +戴著称 +戴著者 +戴著述 +扎着 +扎著书 +扎著作 +扎著名 +扎著录 +扎著称 +扎著者 +扎著述 +打着 +打著书 +打著作 +打著名 +打著录 +打著称 +打著者 +打著述 +扛着 +扛著书 +扛著作 +扛著名 +扛著录 +扛著称 +扛著者 +扛著述 +找不着 +找不著书 +找不著作 +找不著名 +找不著录 +找不著称 +找不著者 +找不著述 +抓着 +抓著书 +抓著作 +抓著名 +抓著录 +抓著称 +抓著者 +抓著述 +披着 +披著书 +披著作 +披著名 +披著录 +披著称 +披著者 +披著述 +抬着 +抬著书 +抬著作 +抬著名 +抬著录 +抬著称 +抬著者 +抬著述 +抱着 +抱著书 +抱著作 +抱著名 +抱著录 +抱著称 +抱著者 +抱著述 +拉着 +拉著书 +拉著作 +拉著名 +拉著录 +拉著称 +拉著者 +拉著述 +拎着 +拎著书 +拎著作 +拎著名 +拎著录 +拎著称 +拎著者 +拎著述 +拖着 +拖著书 +拖著作 +拖著名 +拖著录 +拖著称 +拖著者 +拖著述 +拼着 +拼著书 +拼著作 +拼著名 +拼著录 +拼著称 +拼著者 +拼著述 +拿着 +拿著书 +拿著作 +拿著名 +拿著录 +拿著称 +拿著者 +拿著述 +持着 +持著书 +持著作 +持著名 +持著录 +持著称 +持著者 +持著述 +挑着 +挑著书 +挑著作 +挑著名 +挑著录 +挑著称 +挑著者 +挑著述 +挡着 +挡著书 +挡著作 +挡著名 +挡著录 +挡著称 +挡著者 +挡著述 +挣着 +挣著书 +挣著作 +挣著名 +挣著录 +挣著称 +挣著者 +挣著述 +挥着 +挥著书 +挥著作 +挥著名 +挥著录 +挥著称 +挥著者 +挥著述 +挨着 +挨著书 +挨著作 +挨著名 +挨著录 +挨著称 +挨著者 +挨著述 +捆着 +捆著书 +捆著作 +捆著名 +捆著录 +捆著称 +捆著者 +捆著述 +据着 +据著书 +据著作 +据著名 +据著录 +据著称 +据著者 +据著述 掖着 -衣着 -疑着 -溢着 -艺着 -因着 -印着 -应着 +掖著书 +掖著作 +掖著名 +掖著录 +掖著称 +掖著者 +掖著述 +接着 +接著书 +接著作 +接著名 +接著录 +接著称 +接著者 +接著述 +揉着 +揉著书 +揉著作 +揉著名 +揉著录 +揉著称 +揉著者 +揉著述 +提着 +提著书 +提著作 +提著名 +提著录 +提著称 +提著者 +提著述 +搂着 +搂著书 +搂著作 +搂著名 +搂著录 +搂著称 +搂著者 +搂著述 +摆着 +摆著书 +摆著作 +摆著名 +摆著录 +摆著称 +摆著者 +摆著述 +撼着 +撼著书 +撼著作 +撼著名 +撼著录 +撼著称 +撼著者 +撼著述 +敞着 +敞著书 +敞著作 +敞著名 +敞著录 +敞著称 +敞著者 +敞著述 +数着 +数著书 +数著作 +数著名 +数著录 +数著称 +数著者 +数著述 +斗着 +斗著书 +斗著作 +斗著名 +斗著录 +斗著称 +斗著者 +斗著述 +斥着 +斥著书 +斥著作 +斥著名 +斥著录 +斥著称 +斥著者 +斥著述 +昂着 +昂著书 +昂著作 +昂著名 +昂著录 +昂著称 +昂著者 +昂著述 映着 -用不着 -用着 -用著作 -悠着 +映著书 +映著作 +映著名 +映著录 +映著称 +映著者 +映著述 +晃着 +晃著书 +晃著作 +晃著名 +晃著录 +晃著称 +晃著者 +晃著述 +暗着 +暗著书 +暗著作 +暗著名 +暗著录 +暗著称 +暗著者 +暗著述 有着 -有著名 +有著书 有著作 +有著名 +有著录 +有著称 有著者 -与着 -与著名 -与著作 -与著者 -语着 -豫着 -远着 -跃着 +有著述 +望着 +望著书 +望著作 +望著名 +望著录 +望著称 +望著者 +望著述 +朝着 +朝著书 +朝著作 +朝著名 +朝著录 +朝著称 +朝著者 +朝著述 +本着 +本著书 +本著作 +本著名 +本著录 +本著称 +本著者 +本著述 +杀着 +杀著书 +杀著作 +杀著名 +杀著录 +杀著称 +杀著者 +杀著述 杂着 -载着 -在着 -在著作 -在著名 -在著者 -扎着 -展着 -站着 -战着 -蘸着 -仗着 -找不着 +杂著书 +杂著作 +杂著名 +杂著录 +杂著称 +杂著者 +杂著述 +来着 +来著书 +来著作 +来著名 +来著录 +来著称 +来著者 +来著述 +枕着 +枕著书 +枕著作 +枕著名 +枕著录 +枕著称 +枕著者 +枕著述 +梦着 +梦著书 +梦著作 +梦著名 +梦著录 +梦著称 +梦著者 +梦著述 +梳着 +梳著书 +梳著作 +梳著名 +梳著录 +梳著称 +梳著者 +梳著述 +求着 +求著书 +求著作 +求著名 +求著录 +求著称 +求著者 +求著述 +沉着 +沉著书 +沉著作 +沉著名 +沉著录 +沉著称 +沉著者 +沉著述 +沿着 +沿著书 +沿著作 +沿著名 +沿著录 +沿著称 +沿著者 +沿著述 +活着 +活著书 +活著作 +活著名 +活著录 +活著称 +活著者 +活著述 +流着 +流著书 +流著作 +流著名 +流著录 +流著称 +流著者 +流著述 +浮着 +浮著书 +浮著作 +浮著名 +浮著录 +浮著称 +浮著者 +浮著述 +润着 +润著书 +润著作 +润著名 +润著录 +润著称 +润著者 +润著述 +涵着 +涵著书 +涵著作 +涵著名 +涵著录 +涵著称 +涵著者 +涵著述 +渴着 +渴著书 +渴著作 +渴著名 +渴著录 +渴著称 +渴著者 +渴著述 +溢着 +溢著书 +溢著作 +溢著名 +溢著录 +溢著称 +溢著者 +溢著述 +演着 +演著书 +演著作 +演著名 +演著录 +演著称 +演著者 +演著述 +漫着 +漫著书 +漫著作 +漫著名 +漫著录 +漫著称 +漫著者 +漫著述 +点着 +点著作 +点著名 +点著录 +点著称 +点著者 +点著述 +烧着 +烧著作 +烧著名 +烧著录 +烧著称 +烧著者 +烧著述 照着 -照著名 +照著书 照著作 +照著名 +照著录 +照著称 照著者 -罩着 -贞着 -枕着 -争着 -挣着 -制着 -志着 +照著述 +爱着 +爱著书 +爱著作 +爱著名 +爱著录 +爱著称 +爱著者 +爱著述 +牵着 +牵著书 +牵著作 +牵著名 +牵著录 +牵著称 +牵著者 +牵著述 +犯不着 +独着 +独著书 +独著作 +独著名 +独著录 +独著称 +独著者 +独著述 +猜着 +猜着书 +猜著作 +猜著名 +猜著录 +猜著称 +猜著者 +猜著述 +甜着 +甜著书 +甜著作 +甜著名 +甜著录 +甜著称 +甜著者 +甜著述 +用不着 +用不着书 +用不著作 +用不著名 +用不著录 +用不著称 +用不著者 +用不著述 +用着 +用著书 +用著作 +用著名 +用著录 +用著称 +用著者 +用著述 +留着 +留着书 +留著作 +留著名 +留著录 +留著称 +留著者 +留著述 +疑着 +疑著书 +疑著作 +疑著名 +疑著录 +疑著称 +疑著者 +疑著述 皱着 -住着 -着笔 -着鞭 -着法 -着火 -着急 -着舰 -着脚 -着她 -着紧 -着力 +皱著书 +皱著作 +皱著名 +皱著录 +皱著称 +皱著者 +皱著述 +盛着 +盛著书 +盛著作 +盛著名 +盛著录 +盛著称 +盛著者 +盛著述 +盯着 +盯着书 +盯著作 +盯著名 +盯著录 +盯著称 +盯著者 +盯著述 +盾着 +盾著书 +盾著作 +盾著名 +盾著录 +盾著称 +盾著者 +盾著述 +看着 +看着书 +看著作 +看著名 +看著录 +看著称 +看著者 +看著述 +着业 +着丝 +着么 +着人 +着什么急 +着他 +着令 +着位 +着体 +着你 +着便 着凉 -着陆 -着录 -着落 -着忙 -着迷 +着力 +着劲 +着号 +着呢 +着哩 +着地 着墨 +着声 +着处 +着她 着妳 -着你 -着色 -着什么急 +着姓 +着它 +着定 着实 +着己 +着帐 +着床 +着庸 +着式 +着录 +着心 +着志 +着忙 +着急 +着恼 +着惊 +着想 +着意 +着慌 +着我 着手 +着抹 +着摸 +着撰 着数 -着丝 -着他 -着它 -着祂 -着我 -着想 +着明 +着末 +着极 +着格 +着棋 +着槁 +着气 +着法 +着浅 +着火 +着然 +着甚 +着生 +着疑 +着白 +着相 着眼 +着着 +着祂 +着积 +着稿 +着笔 +着籍 +着紧 +着緑 +着绊 +着绩 +着绯 +着绿 +着肉 +着脚 +着舰 +着色 +着节 +着花 +着莫 +着落 +着藁 着衣 -着意 -着重 -着重 着装 -抓着 -转着 +着要 +着警 +着趣 +着边 +着迷 +着迹 +着重 +着録 +着闻 +着陆 +着雝 +着鞭 +着题 +着魔 +睡不着 +睡不著书 +睡不著作 +睡不著名 +睡不著录 +睡不著称 +睡不著者 +睡不著述 +睡着 +睡著书 +睡著作 +睡著名 +睡著录 +睡著称 +睡著者 +睡著述 +瞒着 +瞒著书 +瞒著作 +瞒著名 +瞒著录 +瞒著称 +瞒著者 +瞒著述 +瞪着 +瞪著书 +瞪著作 +瞪著名 +瞪著录 +瞪著称 +瞪著者 +瞪著述 +福着 +福著书 +福著作 +福著名 +福著录 +福著称 +福著者 +福著述 +空着 +空著书 +空著作 +空著名 +空著录 +空著称 +空著者 +空著述 +穿着 +穿著书 +穿著作 +穿著名 +穿著录 +穿著称 +穿著者 +穿著述 +竖着 +竖著书 +竖著作 +竖著名 +竖著录 +竖著称 +竖著者 +竖著述 +站着 +站著书 +站著作 +站著名 +站著录 +站著称 +站著者 +站著述 +笑着 +笑著书 +笑著作 +笑著名 +笑著录 +笑著称 +笑著者 +笑著述 +管着 +管著书 +管著作 +管著名 +管著录 +管著称 +管著者 +管著述 +绑着 +绑著书 +绑著作 +绑著名 +绑著录 +绑著称 +绑著者 +绑著述 +绕着 +绕著书 +绕著作 +绕著名 +绕著录 +绕著称 +绕著者 +绕著述 +缠着 +缠著书 +缠著作 +缠著名 +缠著录 +缠著称 +缠著者 +缠著述 +罩着 +罩著书 +罩著作 +罩著名 +罩著录 +罩著称 +罩著者 +罩著述 +美着 +美著书 +美著作 +美著名 +美著录 +美著称 +美著者 +美著述 +耀着 +耀著书 +耀著作 +耀著名 +耀著录 +耀著称 +耀著者 +耀著述 +考着 +考著书 +考著作 +考著名 +考著录 +考著称 +考著者 +考著述 +背着 +背著书 +背著作 +背著名 +背著录 +背著称 +背著者 +背著述 +胶着 +胶著书 +胶著作 +胶著名 +胶著录 +胶著称 +胶著者 +胶著述 +艺着 +艺著书 +艺著作 +艺著名 +艺著录 +艺著称 +艺著者 +艺著述 +苦着 +苦著书 +苦著作 +苦著名 +苦著录 +苦著称 +苦著者 +苦著述 +获着 +获著书 +获著作 +获著名 +获著录 +获著称 +获著者 +获著述 +落着 +落著书 +落著作 +落著名 +落著录 +落著称 +落著者 +落著述 +蒙着 +蒙著书 +蒙著作 +蒙著名 +蒙著录 +蒙著称 +蒙著者 +蒙著述 +藏着 +藏著书 +藏著作 +藏著名 +藏著录 +藏著称 +藏著者 +藏著述 +蘸着 +蘸著书 +蘸著作 +蘸著名 +蘸著录 +蘸著称 +蘸著者 +蘸著述 +行着 +行著书 +行著作 +行著名 +行著录 +行著称 +行著者 +行著述 +衣着 +衣著书 +衣著作 +衣著名 +衣著录 +衣著称 +衣著者 +衣著述 装着 +装著书 +装著作 +装著名 +装著录 +装著称 +装著者 +装著述 +裹着 +裹著书 +裹著作 +裹著名 +裹著录 +裹著称 +裹著者 +裹著述 +见着 +见著书 +见著作 +见著名 +见著录 +见著称 +见著者 +见著述 +记着 +记著书 +记著作 +记著名 +记著录 +记著称 +记著者 +记著述 +试着 +试著书 +试著作 +试著名 +试著录 +试著称 +试著者 +试著述 +语着 +语著书 +语著作 +语著名 +语著录 +语著称 +语著者 +语著述 +豫着 +豫著书 +豫著作 +豫著名 +豫著录 +豫著称 +豫著者 +豫著述 +贞着 +贞著书 +贞著作 +贞著名 +贞著录 +贞著称 +贞著者 +贞著述 +走着 +走著书 +走著作 +走著名 +走著录 +走著称 +走著者 +走著述 +赶着 +赶著书 +赶著作 +赶著名 +赶著录 +赶著称 +赶著者 +赶著述 +趴着 +趴著书 +趴著作 +趴著名 +趴著录 +趴著称 +趴著者 +趴著述 +跃着 +跃著书 +跃著作 +跃著名 +跃著录 +跃著称 +跃著者 +跃著述 +跑着 +跑著书 +跑著作 +跑著名 +跑著录 +跑著称 +跑著者 +跑著述 +跟着 +跟著书 +跟著作 +跟著名 +跟著录 +跟著称 +跟著者 +跟著述 +跪着 +跪著书 +跪著作 +跪著名 +跪著录 +跪著称 +跪著者 +跪著述 +跳着 +跳著书 +跳著作 +跳著名 +跳著录 +跳著称 +跳著者 +跳著述 +踏着 +踏著书 +踏著作 +踏著名 +踏著录 +踏著称 +踏著者 +踏著述 +踩着 +踩著书 +踩著作 +踩著名 +踩著录 +踩著称 +踩著者 +踩著述 +身着 +身著书 +身著作 +身著名 +身著录 +身著称 +身著者 +身著述 +躺着 +躺著书 +躺著作 +躺著名 +躺著录 +躺著称 +躺著者 +躺著述 +转着 +转著书 +转著作 +转著名 +转著录 +转著称 +转著者 +转著述 +载着 +载著书 +载著作 +载著名 +载著录 +载著称 +载著者 +载著述 +达着 +达著书 +达著作 +达著名 +达著录 +达著称 +达著者 +达著述 +远着 +远著书 +远著作 +远著名 +远著录 +远著称 +远著者 +远著述 +连着 +连著书 +连著作 +连著名 +连著录 +连著称 +连著者 +连著述 追着 +追著书 +追著作 +追著名 +追著录 +追著称 +追著者 +追著述 +逆着 +逆著书 +逆著作 +逆著名 +逆著录 +逆著称 +逆著者 +逆著述 +逼着 +逼著书 +逼著作 +逼著名 +逼著录 +逼著称 +逼著者 +逼著述 +遇着 +遇著书 +遇著作 +遇著名 +遇著录 +遇著称 +遇著者 +遇著述 +配着 +配著书 +配著作 +配著名 +配著录 +配著称 +配著者 +配著述 +酿着 +酿著书 +酿著作 +酿著名 +酿著录 +酿著称 +酿著者 +酿著述 +铺着 +铺著书 +铺著作 +铺著名 +铺著录 +铺著称 +铺著者 +铺著述 +闭着 +闭著书 +闭著作 +闭著名 +闭著录 +闭著称 +闭著者 +闭著述 +闲着 +闲著书 +闲著作 +闲著名 +闲著录 +闲著称 +闲著者 +闲著述 +附着 +附著书 +附著作 +附著名 +附著录 +附著称 +附著者 +附著述 +陋着 +陋著书 +陋著作 +陋著名 +陋著录 +陋著称 +陋著者 +陋著述 +陪着 +陪著书 +陪著作 +陪著名 +陪著录 +陪著称 +陪著者 +陪著述 +随着 +随著书 +随著作 +随著名 +随著录 +随著称 +随著者 +随著述 +隔着 +隔著书 +隔著作 +隔著名 +隔著录 +隔著称 +隔著者 +隔著述 +雅着 +雅著书 +雅著作 +雅著名 +雅著录 +雅著称 +雅著者 +雅著述 +顶着 +顶著书 +顶著作 +顶著名 +顶著录 +顶著称 +顶著者 +顶著述 +顺着 +顺著书 +顺著作 +顺著名 +顺著录 +顺著称 +顺著者 +顺著述 +领着 +领著书 +领著作 +领著名 +领著录 +领著称 +领著者 +领著述 +飘着 +飘著书 +飘著作 +飘著名 +飘著录 +飘著称 +飘著者 +飘著述 +驾着 +驾著书 +驾著作 +驾著名 +驾著录 +驾著称 +驾著者 +驾著述 +骂着 +骂著书 +骂著作 +骂著名 +骂著录 +骂著称 +骂著者 +骂著述 +骑着 +骑著书 +骑著作 +骑著名 +骑著录 +骑著称 +骑著者 +骑著述 +骗着 +骗著书 +骗著作 +骗著名 +骗著录 +骗著称 +骗著者 +骗著述 +高着 +高著书 +高著作 +高著名 +高著录 +高著称 +高著者 +高著述 髭着 -走着 -坐着 -做着 -含着 -含著名 -含著作 -含著者 -涵着 -涵著名 -涵著作 -涵著者 -演着 -演著名 -演著作 -演著者 -保障着 -保障著名 -保障著作 -保障著者 +髭著书 +髭著作 +髭著名 +髭著录 +髭著称 +髭著者 +髭著述 黏着 -胶着 -附着 -代表着 -代表著名 -代表著作 -代表著者 -着地 -浮着 -写着 -写著作 -写著名 -遇着 -杀着 -杀著名 -杀著作 -杀著者 +黏著书 +黏著作 +黏著名 +黏著录 +黏著称 +黏著者 +黏著述 +新著龙虎门 +护着 +护著书 +护著作 +护著名 +护著录 +护著称 +护著者 +护著述 於乎 於戏 魏徵 @@ -391,3 +2163,38 @@ 於菟 於潜县 馀年 +石碁镇 +因著《 +因著〈 +李泽钜 +於祥玉 +於崇文 +覆蓋 +五箇山 +麽麽 +幺厮 +幺半群 +幺元 +幺爹 +幺叔 +幺舅 +幺爸 +幺妈 +幺姨 +幺娘 +幺妹 +幺小 +幺姓 +姓幺 +幺氏 +麽氏 +幺蛾子 +幺麽 +幺麽小丑 +幺凤 +幺二三 +幺篇 +幺谦 +麴义 +麴英 +麯崇裕
\ No newline at end of file diff --git a/includes/zhtable/simpphrases_exclude.manual b/includes/zhtable/simpphrases_exclude.manual index e69de29b..a8f61e09 100644 --- a/includes/zhtable/simpphrases_exclude.manual +++ b/includes/zhtable/simpphrases_exclude.manual @@ -0,0 +1,18 @@ +整飭 +後 +谘 +彷佛 +三番四复 +三复 +藉 +关於 +对於 +属於 +至於 +夥计 +薹 +嚇 +醣 +捱 +簑 +樑
\ No newline at end of file diff --git a/includes/zhtable/toCN.manual b/includes/zhtable/toCN.manual index feeca9dc..5bfcc00c 100644 --- a/includes/zhtable/toCN.manual +++ b/includes/zhtable/toCN.manual @@ -13,7 +13,6 @@ 光碟 光盘 光碟機 光驱 全形 全角 -共用 共享 載入 加载 半形 半角 變數 变量 @@ -41,6 +40,8 @@ 磁碟 磁盘 磁軌 磁道 程式控制 程控 +遠程控制 远程控制 +远程控制 远程控制 運算元 算子 演算法 算法 晶片 芯片 @@ -49,7 +50,22 @@ 軟碟機 软驱 快閃記憶體 快闪存储器 滑鼠 鼠标 -進位 进制 +二進位 二进制 +滿二進位 满二进位 +六進位 六进制 +滿六進位 满六进位 +滿十六進位 满十六进位 +八進位 八进制 +滿八進位 满八进位 +十進位 十进制 +滿十進位 满十进位 +16進位 16进位 +滿16進位 满16进位 +二進位制 二进位制 +六進位制 六进位制 +八進位制 八进位制 +十進位制 十进位制 +16進位制 16进位制 互動式 交互式 優先順序 优先级 感測 传感 @@ -89,8 +105,6 @@ 二極管 二极管 三極體 三极管 三極管 三极管 -數位 数码 -數碼 数码 軟體 软件 軟件 软件 網路 网络 @@ -98,8 +112,8 @@ 人工智慧 人工智能 太空梭 航天飞机 穿梭機 航天飞机 -網際網路 因特网 -互聯網 因特网 +網際網路 互联网 +互聯網 互联网 機械人 机器人 機器人 机器人 行動電話 移动电话 @@ -109,7 +123,6 @@ 短訊 短信 簡訊 短信 烏茲別克 乌兹别克斯坦 -查德 乍得 葉門 也门 伯利茲 伯利兹 貝里斯 伯利兹 @@ -122,7 +135,6 @@ 迦納 加纳 加彭 加蓬 波札那 博茨瓦纳 -卡達 卡塔尔 盧安達 卢旺达 瓜地馬拉 危地马拉 厄瓜多爾 厄瓜多尔 @@ -155,7 +167,6 @@ 奈及利亞 尼日利亚 尼日爾 尼日尔 尼日尔 尼日尔 -尼日 尼日尔 巴貝多 巴巴多斯 巴布亞紐幾內亞 巴布亚新几内亚 布基納法索 布基纳法索 @@ -215,6 +226,7 @@ 屋价 房价 計程車 出租车 公車 公共汽车 +公車上書 公车上书 單車 自行车 節慶 节日 芝士 乾酪 @@ -223,7 +235,6 @@ 忌廉 奶油 桌球 台球 撞球 台球 -雪糕 冰淇淋 衞生 卫生 衛生 卫生 賓士 奔驰 @@ -248,3 +259,14 @@ 舒麥加 迈克尔·舒马赫 希特拉 希特勒 黛安娜 戴安娜 +榴槤 榴莲 +榴梿 榴莲 +矽 硅 +矽肺 矽肺 +矽塵 矽尘 +矽尘 矽尘 +矽鋼 矽钢 +矽钢 矽钢 +侏儸紀 侏罗纪 +甚麽 什么 +甚麼 什么
\ No newline at end of file diff --git a/includes/zhtable/toHK.manual b/includes/zhtable/toHK.manual index 916b4020..a39d6409 100644 --- a/includes/zhtable/toHK.manual +++ b/includes/zhtable/toHK.manual @@ -14,7 +14,6 @@ 硬體 硬件 二極體 二極管 三極體 三極管 -數位 數碼 軟體 軟件 網路 網絡 人工智慧 人工智能 @@ -41,7 +40,6 @@ 迦納 加納 加彭 加蓬 波札那 博茨瓦納 -卡達 卡塔爾 盧安達 盧旺達 瓜地馬拉 危地馬拉 厄瓜多尔 厄瓜多爾 @@ -63,7 +61,6 @@ 吉里巴斯 基里巴斯 塞普勒斯 塞浦路斯 塞席爾 塞舌爾 -多米尼克 多明尼加國 安地卡及巴布達 安提瓜和巴布達 尼日利亚 尼日利亞 尼日利亞 尼日利亞 @@ -119,6 +116,7 @@ 計程車 的士 出租车 的士 公車 巴士 +公車上書 公車上書 自行车 單車 犬只 狗隻 台球 桌球 @@ -147,3 +145,2001 @@ 希特勒 希特拉 狄安娜 戴安娜 黛安娜 戴安娜 +颁布 頒佈 +頒布 頒佈 +挨著 挨着 +愛著 愛着 +暗著 暗着 +昂著 昂着 +擺著 擺着 +伴著 伴着 +辦著 辦着 +幫著 幫着 +綁著 綁着 +抱著 抱着 +背著 背着 +備著 備着 +本著 本着 +逼著 逼着 +閉著 閉着 +變著 變着 +猜著 猜着 +踩著 踩着 +藏著 藏着 +側著 側着 +纏著 纏着 +敞著 敞着 +唱著 唱着 +朝著 朝着 +沉著 沉着 +乘著 乘着 +持著 持着 +斥著 斥着 +醜著 醜着 +穿著 穿着 +吹著 吹着 +達著 達着 +打著 打着 +待著 待着 +帶著 帶着 +戴著 戴着 +當著 當着 +擋著 擋着 +得著 得着 +瞪著 瞪着 +低著 低着 +點著 點着 +盯著 盯着 +頂著 頂着 +定著 定着 +動著 動着 +鬥著 鬥着 +獨著 獨着 +對著 對着 +盾著 盾着 +犯不著 犯不着 +福著 福着 +趕著 趕着 +高著 高着 +隔著 隔着 +跟著 跟着 +孤著 孤着 +關著 關着 +管著 管着 +慣著 慣着 +光著 光着 +跪著 跪着 +裹著 裹着 +撼著 撼着 +喝著 喝着 +候著 候着 +懷著 懷着 +晃著 晃着 +揮著 揮着 +活著 活着 +獲著 獲着 +獲著 獲着 +急著 急着 +記著 記着 +冀著 冀着 +夾著 夾着 +駕著 駕着 +見著 見着 +閑著 閑着 +叫著 叫着 +接著 接着 +借著 借着 +借著 借着 +據著 據着 +開著 開着 +看著 看着 +康著 康着 +扛著 扛着 +考著 考着 +渴著 渴着 +刻著 刻着 +空著 空着 +哭著 哭着 +苦著 苦着 +捆著 捆着 +困著 困着 +拉著 拉着 +來著 來着 +樂著 樂着 +努力著 努力着 +麗著 麗着 +連著 連着 +戀著 戀着 +涼著 涼着 +亮著 亮着 +臨著 臨着 +拎著 拎着 +領著 領着 +流著 流着 +留著 留着 +摟著 摟着 +陋著 陋着 +落著 落着 +罵著 罵着 +瞞著 瞞着 +漫著 漫着 +忙著 忙着 +冒著 冒着 +美著 美着 +夢著 夢着 +蒙著 蒙着 +拿著 拿着 +逆著 逆着 +釀著 釀着 +努著 努着 +趴著 趴着 +跑著 跑着 +陪著 陪着 +配著 配着 +披著 披着 +騙著 騙着 +飄著 飄着 +拼著 拼着 +鋪著 鋪着 +騎著 騎着 +牽著 牽着 +求著 求着 +去著 去着 +嚷著 嚷着 +繞著 繞着 +忍著 忍着 +揉著 揉着 +潤著 潤着 +燒著 燒着 +身著 身着 +沉著 沉着 +盛著 盛着 +試著 試着 +守著 守着 +受著 受着 +梳著 梳着 +豎著 豎着 +數著 數着 +睡不著 睡不着 +睡著 睡着 +順著 順着 +隨著 隨着 +踏著 踏着 +抬著 抬着 +躺著 躺着 +提著 提着 +甜著 甜着 +挑著 挑着 +跳著 跳着 +聽著 聽着 +聽著 聽着 +偷著 偷着 +拖著 拖着 +望著 望着 +圍著 圍着 +味著 味着 +想著 想着 +響著 響着 +向著 向着 +笑著 笑着 +心著 心着 +信著 信着 +行著 行着 +性著 性着 +學著 學着 +尋著 尋着 +循著 循着 +壓著 壓着 +雅著 雅着 +沿著 沿着 +耀著 耀着 +掖著 掖着 +衣著 衣着 +疑著 疑着 +溢著 溢着 +藝著 藝着 +因著 因着 +印著 印着 +應著 應着 +映著 映着 +用不著 用不着 +用著 用着 +悠著 悠着 +有著 有着 +與著 與着 +語著 語着 +豫著 豫着 +遠著 遠着 +躍著 躍着 +雜著 雜着 +載著 載着 +在著 在着 +紮著 紮着 +展著 展着 +站著 站着 +戰著 戰着 +蘸著 蘸着 +仗著 仗着 +找不著 找不着 +照著 照着 +罩著 罩着 +貞著 貞着 +枕著 枕着 +爭著 爭着 +掙著 掙着 +制著 制着 +志著 志着 +皺著 皺着 +住著 住着 +抓著 抓着 +轉著 轉着 +裝著 裝着 +追著 追着 +髭著 髭着 +走著 走着 +坐著 坐着 +做著 做着 +含著 含着 +涵著 涵着 +演著 演着 +保障著 保障着 +黏著 黏着 +膠著 膠着 +附著 附着 +代表著 代表着 +浮著 浮着 +寫著 寫着 +遇著 遇着 +殺著 殺着 +著筆 着筆 +著鞭 着鞭 +著法 着法 +著火 着火 +著急 着急 +著艦 着艦 +著腳 着腳 +著她 着她 +著緊 着緊 +著力 着力 +著涼 着涼 +著陸 着陸 +著錄 着錄 +著落 着落 +著忙 着忙 +著迷 着迷 +著墨 着墨 +著妳 着妳 +著你 着你 +著色 着色 +著什麼急 着什麼急 +著實 着實 +著手 着手 +著數 着數 +著絲 着絲 +著他 着他 +著它 着它 +著祂 着祂 +著我 着我 +著想 着想 +著眼 着眼 +著衣 着衣 +著意 着意 +著重 着重 +著重 着重 +著裝 着裝 +著地 着地 +不著邊際 不着邊際 +不著痕跡 不着痕跡 +挨著作 挨著作 +挨著者 挨著者 +挨著名 挨著名 +挨著述 挨著述 +挨著稱 挨著稱 +挨著錄 挨著錄 +挨著書 挨著書 +愛著作 愛著作 +愛著者 愛著者 +愛著名 愛著名 +愛著述 愛著述 +愛著稱 愛著稱 +愛著錄 愛著錄 +愛著書 愛著書 +暗著作 暗著作 +暗著者 暗著者 +暗著名 暗著名 +暗著述 暗著述 +暗著稱 暗著稱 +暗著錄 暗著錄 +暗著書 暗著書 +昂著作 昂著作 +昂著者 昂著者 +昂著名 昂著名 +昂著述 昂著述 +昂著稱 昂著稱 +昂著錄 昂著錄 +昂著書 昂著書 +擺著作 擺著作 +擺著者 擺著者 +擺著名 擺著名 +擺著述 擺著述 +擺著稱 擺著稱 +擺著錄 擺著錄 +擺著書 擺著書 +伴著作 伴著作 +伴著者 伴著者 +伴著名 伴著名 +伴著述 伴著述 +伴著稱 伴著稱 +伴著錄 伴著錄 +伴著書 伴著書 +辦著作 辦著作 +辦著者 辦著者 +辦著名 辦著名 +辦著述 辦著述 +辦著稱 辦著稱 +辦著錄 辦著錄 +辦著書 辦著書 +幫著作 幫著作 +幫著者 幫著者 +幫著名 幫著名 +幫著述 幫著述 +幫著稱 幫著稱 +幫著錄 幫著錄 +幫著書 幫著書 +綁著作 綁著作 +綁著者 綁著者 +綁著名 綁著名 +綁著述 綁著述 +綁著稱 綁著稱 +綁著錄 綁著錄 +綁著書 綁著書 +抱著作 抱著作 +抱著者 抱著者 +抱著名 抱著名 +抱著述 抱著述 +抱著稱 抱著稱 +抱著錄 抱著錄 +抱著書 抱著書 +背著作 背著作 +背著者 背著者 +背著名 背著名 +背著述 背著述 +背著稱 背著稱 +背著錄 背著錄 +背著書 背著書 +備著作 備著作 +備著者 備著者 +備著名 備著名 +備著述 備著述 +備著稱 備著稱 +備著錄 備著錄 +備著書 備著書 +本著作 本著作 +本著者 本著者 +本著名 本著名 +本著述 本著述 +本著稱 本著稱 +本著錄 本著錄 +本著書 本著書 +逼著作 逼著作 +逼著者 逼著者 +逼著名 逼著名 +逼著述 逼著述 +逼著稱 逼著稱 +逼著錄 逼著錄 +逼著書 逼著書 +閉著作 閉著作 +閉著者 閉著者 +閉著名 閉著名 +閉著述 閉著述 +閉著稱 閉著稱 +閉著錄 閉著錄 +閉著書 閉著書 +變著作 變著作 +變著者 變著者 +變著名 變著名 +變著述 變著述 +變著稱 變著稱 +變著錄 變著錄 +變著書 變著書 +猜著作 猜著作 +猜著者 猜著者 +猜著名 猜著名 +猜著述 猜著述 +猜著稱 猜著稱 +猜著錄 猜著錄 +猜著書 猜著書 +踩著作 踩著作 +踩著者 踩著者 +踩著名 踩著名 +踩著述 踩著述 +踩著稱 踩著稱 +踩著錄 踩著錄 +踩著書 踩著書 +藏著作 藏著作 +藏著者 藏著者 +藏著名 藏著名 +藏著述 藏著述 +藏著稱 藏著稱 +藏著錄 藏著錄 +藏著書 藏著書 +側著作 側著作 +側著者 側著者 +側著名 側著名 +側著述 側著述 +側著稱 側著稱 +側著錄 側著錄 +側著書 側著書 +纏著作 纏著作 +纏著者 纏著者 +纏著名 纏著名 +纏著述 纏著述 +纏著稱 纏著稱 +纏著錄 纏著錄 +纏著書 纏著書 +敞著作 敞著作 +敞著者 敞著者 +敞著名 敞著名 +敞著述 敞著述 +敞著稱 敞著稱 +敞著錄 敞著錄 +敞著書 敞著書 +唱著作 唱著作 +唱著者 唱著者 +唱著名 唱著名 +唱著述 唱著述 +唱著稱 唱著稱 +唱著錄 唱著錄 +唱著書 唱著書 +朝著作 朝著作 +朝著者 朝著者 +朝著名 朝著名 +朝著述 朝著述 +朝著稱 朝著稱 +朝著錄 朝著錄 +朝著書 朝著書 +沉著作 沉著作 +沉著者 沉著者 +沉著名 沉著名 +沉著述 沉著述 +沉著稱 沉著稱 +沉著錄 沉著錄 +沉著書 沉著書 +乘著作 乘著作 +乘著者 乘著者 +乘著名 乘著名 +乘著述 乘著述 +乘著稱 乘著稱 +乘著錄 乘著錄 +乘著書 乘著書 +持著作 持著作 +持著者 持著者 +持著名 持著名 +持著述 持著述 +持著稱 持著稱 +持著錄 持著錄 +持著書 持著書 +斥著作 斥著作 +斥著者 斥著者 +斥著名 斥著名 +斥著述 斥著述 +斥著稱 斥著稱 +斥著錄 斥著錄 +斥著書 斥著書 +醜著作 醜著作 +醜著者 醜著者 +醜著名 醜著名 +醜著述 醜著述 +醜著稱 醜著稱 +醜著錄 醜著錄 +醜著書 醜著書 +穿著作 穿著作 +穿著者 穿著者 +穿著名 穿著名 +穿著述 穿著述 +穿著稱 穿著稱 +穿著錄 穿著錄 +穿著書 穿著書 +吹著作 吹著作 +吹著者 吹著者 +吹著名 吹著名 +吹著述 吹著述 +吹著稱 吹著稱 +吹著錄 吹著錄 +吹著書 吹著書 +達著作 達著作 +達著者 達著者 +達著名 達著名 +達著述 達著述 +達著稱 達著稱 +達著錄 達著錄 +達著書 達著書 +打著作 打著作 +打著者 打著者 +打著名 打著名 +打著述 打著述 +打著稱 打著稱 +打著錄 打著錄 +打著書 打著書 +待著作 待著作 +待著者 待著者 +待著名 待著名 +待著述 待著述 +待著稱 待著稱 +待著錄 待著錄 +待著書 待著書 +帶著作 帶著作 +帶著者 帶著者 +帶著名 帶著名 +帶著述 帶著述 +帶著稱 帶著稱 +帶著錄 帶著錄 +帶著書 帶著書 +戴著作 戴著作 +戴著者 戴著者 +戴著名 戴著名 +戴著述 戴著述 +戴著稱 戴著稱 +戴著錄 戴著錄 +戴著書 戴著書 +當著作 當著作 +當著者 當著者 +當著名 當著名 +當著述 當著述 +當著稱 當著稱 +當著錄 當著錄 +當著書 當著書 +擋著作 擋著作 +擋著者 擋著者 +擋著名 擋著名 +擋著述 擋著述 +擋著稱 擋著稱 +擋著錄 擋著錄 +擋著書 擋著書 +得著作 得著作 +得著者 得著者 +得著名 得著名 +得著述 得著述 +得著稱 得著稱 +得著錄 得著錄 +得著書 得著書 +瞪著作 瞪著作 +瞪著者 瞪著者 +瞪著名 瞪著名 +瞪著述 瞪著述 +瞪著稱 瞪著稱 +瞪著錄 瞪著錄 +瞪著書 瞪著書 +低著作 低著作 +低著者 低著者 +低著名 低著名 +低著述 低著述 +低著稱 低著稱 +低著錄 低著錄 +低著書 低著書 +點著作 點著作 +點著者 點著者 +點著名 點著名 +點著述 點著述 +點著稱 點著稱 +點著錄 點著錄 +點著書 點著書 +盯著作 盯著作 +盯著者 盯著者 +盯著名 盯著名 +盯著述 盯著述 +盯著稱 盯著稱 +盯著錄 盯著錄 +盯著書 盯著書 +頂著作 頂著作 +頂著者 頂著者 +頂著名 頂著名 +頂著述 頂著述 +頂著稱 頂著稱 +頂著錄 頂著錄 +頂著書 頂著書 +定著作 定著作 +定著者 定著者 +定著名 定著名 +定著述 定著述 +定著稱 定著稱 +定著錄 定著錄 +定著書 定著書 +動著作 動著作 +動著者 動著者 +動著名 動著名 +動著述 動著述 +動著稱 動著稱 +動著錄 動著錄 +動著書 動著書 +鬥著作 鬥著作 +鬥著者 鬥著者 +鬥著名 鬥著名 +鬥著述 鬥著述 +鬥著稱 鬥著稱 +鬥著錄 鬥著錄 +鬥著書 鬥著書 +獨著作 獨著作 +獨著者 獨著者 +獨著名 獨著名 +獨著述 獨著述 +獨著稱 獨著稱 +獨著錄 獨著錄 +獨著書 獨著書 +對著作 對著作 +對著者 對著者 +對著名 對著名 +對著述 對著述 +對著稱 對著稱 +對著錄 對著錄 +對著書 對著書 +盾著作 盾著作 +盾著者 盾著者 +盾著名 盾著名 +盾著述 盾著述 +盾著稱 盾著稱 +盾著錄 盾著錄 +盾著書 盾著書 +犯不著作 犯不著作 +犯不著者 犯不著者 +犯不著名 犯不著名 +犯不著述 犯不著述 +犯不著稱 犯不著稱 +犯不著錄 犯不著錄 +犯不著書 犯不著書 +福著作 福著作 +福著者 福著者 +福著名 福著名 +福著述 福著述 +福著稱 福著稱 +福著錄 福著錄 +福著書 福著書 +趕著作 趕著作 +趕著者 趕著者 +趕著名 趕著名 +趕著述 趕著述 +趕著稱 趕著稱 +趕著錄 趕著錄 +趕著書 趕著書 +高著作 高著作 +高著者 高著者 +高著名 高著名 +高著述 高著述 +高著稱 高著稱 +高著錄 高著錄 +高著書 高著書 +隔著作 隔著作 +隔著者 隔著者 +隔著名 隔著名 +隔著述 隔著述 +隔著稱 隔著稱 +隔著錄 隔著錄 +隔著書 隔著書 +跟著作 跟著作 +跟著者 跟著者 +跟著名 跟著名 +跟著述 跟著述 +跟著稱 跟著稱 +跟著錄 跟著錄 +跟著書 跟著書 +孤著作 孤著作 +孤著者 孤著者 +孤著名 孤著名 +孤著述 孤著述 +孤著稱 孤著稱 +孤著錄 孤著錄 +孤著書 孤著書 +關著作 關著作 +關著者 關著者 +關著名 關著名 +關著述 關著述 +關著稱 關著稱 +關著錄 關著錄 +關著書 關著書 +管著作 管著作 +管著者 管著者 +管著名 管著名 +管著述 管著述 +管著稱 管著稱 +管著錄 管著錄 +管著書 管著書 +慣著作 慣著作 +慣著者 慣著者 +慣著名 慣著名 +慣著述 慣著述 +慣著稱 慣著稱 +慣著錄 慣著錄 +慣著書 慣著書 +光著作 光著作 +光著者 光著者 +光著名 光著名 +光著述 光著述 +光著稱 光著稱 +光著錄 光著錄 +光著書 光著書 +跪著作 跪著作 +跪著者 跪著者 +跪著名 跪著名 +跪著述 跪著述 +跪著稱 跪著稱 +跪著錄 跪著錄 +跪著書 跪著書 +裹著作 裹著作 +裹著者 裹著者 +裹著名 裹著名 +裹著述 裹著述 +裹著稱 裹著稱 +裹著錄 裹著錄 +裹著書 裹著書 +撼著作 撼著作 +撼著者 撼著者 +撼著名 撼著名 +撼著述 撼著述 +撼著稱 撼著稱 +撼著錄 撼著錄 +撼著書 撼著書 +喝著作 喝著作 +喝著者 喝著者 +喝著名 喝著名 +喝著述 喝著述 +喝著稱 喝著稱 +喝著錄 喝著錄 +喝著書 喝著書 +候著作 候著作 +候著者 候著者 +候著名 候著名 +候著述 候著述 +候著稱 候著稱 +候著錄 候著錄 +候著書 候著書 +懷著作 懷著作 +懷著者 懷著者 +懷著名 懷著名 +懷著述 懷著述 +懷著稱 懷著稱 +懷著錄 懷著錄 +懷著書 懷著書 +晃著作 晃著作 +晃著者 晃著者 +晃著名 晃著名 +晃著述 晃著述 +晃著稱 晃著稱 +晃著錄 晃著錄 +晃著書 晃著書 +揮著作 揮著作 +揮著者 揮著者 +揮著名 揮著名 +揮著述 揮著述 +揮著稱 揮著稱 +揮著錄 揮著錄 +揮著書 揮著書 +活著作 活著作 +活著者 活著者 +活著名 活著名 +活著述 活著述 +活著稱 活著稱 +活著錄 活著錄 +活著書 活著書 +獲著作 獲著作 +獲著者 獲著者 +獲著名 獲著名 +獲著述 獲著述 +獲著稱 獲著稱 +獲著錄 獲著錄 +獲著書 獲著書 +獲著作 獲著作 +獲著者 獲著者 +獲著名 獲著名 +獲著述 獲著述 +獲著稱 獲著稱 +獲著錄 獲著錄 +獲著書 獲著書 +急著作 急著作 +急著者 急著者 +急著名 急著名 +急著述 急著述 +急著稱 急著稱 +急著錄 急著錄 +急著書 急著書 +記著作 記著作 +記著者 記著者 +記著名 記著名 +記著述 記著述 +記著稱 記著稱 +記著錄 記著錄 +記著書 記著書 +冀著作 冀著作 +冀著者 冀著者 +冀著名 冀著名 +冀著述 冀著述 +冀著稱 冀著稱 +冀著錄 冀著錄 +冀著書 冀著書 +夾著作 夾著作 +夾著者 夾著者 +夾著名 夾著名 +夾著述 夾著述 +夾著稱 夾著稱 +夾著錄 夾著錄 +夾著書 夾著書 +駕著作 駕著作 +駕著者 駕著者 +駕著名 駕著名 +駕著述 駕著述 +駕著稱 駕著稱 +駕著錄 駕著錄 +駕著書 駕著書 +見著作 見著作 +見著者 見著者 +見著名 見著名 +見著述 見著述 +見著稱 見著稱 +見著錄 見著錄 +見著書 見著書 +閑著作 閑著作 +閑著者 閑著者 +閑著名 閑著名 +閑著述 閑著述 +閑著稱 閑著稱 +閑著錄 閑著錄 +閑著書 閑著書 +叫著作 叫著作 +叫著者 叫著者 +叫著名 叫著名 +叫著述 叫著述 +叫著稱 叫著稱 +叫著錄 叫著錄 +叫著書 叫著書 +接著作 接著作 +接著者 接著者 +接著名 接著名 +接著述 接著述 +接著稱 接著稱 +接著錄 接著錄 +接著書 接著書 +借著作 借著作 +借著者 借著者 +借著名 借著名 +借著述 借著述 +借著稱 借著稱 +借著錄 借著錄 +借著書 借著書 +借著作 借著作 +借著者 借著者 +借著名 借著名 +借著述 借著述 +借著稱 借著稱 +借著錄 借著錄 +借著書 借著書 +據著作 據著作 +據著者 據著者 +據著名 據著名 +據著述 據著述 +據著稱 據著稱 +據著錄 據著錄 +據著書 據著書 +開著作 開著作 +開著者 開著者 +開著名 開著名 +開著述 開著述 +開著稱 開著稱 +開著錄 開著錄 +開著書 開著書 +看著作 看著作 +看著者 看著者 +看著名 看著名 +看著述 看著述 +看著稱 看著稱 +看著錄 看著錄 +看著書 看著書 +康著作 康著作 +康著者 康著者 +康著名 康著名 +康著述 康著述 +康著稱 康著稱 +康著錄 康著錄 +康著書 康著書 +扛著作 扛著作 +扛著者 扛著者 +扛著名 扛著名 +扛著述 扛著述 +扛著稱 扛著稱 +扛著錄 扛著錄 +扛著書 扛著書 +考著作 考著作 +考著者 考著者 +考著名 考著名 +考著述 考著述 +考著稱 考著稱 +考著錄 考著錄 +考著書 考著書 +渴著作 渴著作 +渴著者 渴著者 +渴著名 渴著名 +渴著述 渴著述 +渴著稱 渴著稱 +渴著錄 渴著錄 +渴著書 渴著書 +刻著作 刻著作 +刻著者 刻著者 +刻著名 刻著名 +刻著述 刻著述 +刻著稱 刻著稱 +刻著錄 刻著錄 +刻著書 刻著書 +空著作 空著作 +空著者 空著者 +空著名 空著名 +空著述 空著述 +空著稱 空著稱 +空著錄 空著錄 +空著書 空著書 +哭著作 哭著作 +哭著者 哭著者 +哭著名 哭著名 +哭著述 哭著述 +哭著稱 哭著稱 +哭著錄 哭著錄 +哭著書 哭著書 +苦著作 苦著作 +苦著者 苦著者 +苦著名 苦著名 +苦著述 苦著述 +苦著稱 苦著稱 +苦著錄 苦著錄 +苦著書 苦著書 +捆著作 捆著作 +捆著者 捆著者 +捆著名 捆著名 +捆著述 捆著述 +捆著稱 捆著稱 +捆著錄 捆著錄 +捆著書 捆著書 +困著作 困著作 +困著者 困著者 +困著名 困著名 +困著述 困著述 +困著稱 困著稱 +困著錄 困著錄 +困著書 困著書 +拉著作 拉著作 +拉著者 拉著者 +拉著名 拉著名 +拉著述 拉著述 +拉著稱 拉著稱 +拉著錄 拉著錄 +拉著書 拉著書 +來著作 來著作 +來著者 來著者 +來著名 來著名 +來著述 來著述 +來著稱 來著稱 +來著錄 來著錄 +來著書 來著書 +樂著作 樂著作 +樂著者 樂著者 +樂著名 樂著名 +樂著述 樂著述 +樂著稱 樂著稱 +樂著錄 樂著錄 +樂著書 樂著書 +努力著作 努力著作 +努力著者 努力著者 +努力著名 努力著名 +努力著述 努力著述 +努力著稱 努力著稱 +努力著錄 努力著錄 +努力著書 努力著書 +麗著作 麗著作 +麗著者 麗著者 +麗著名 麗著名 +麗著述 麗著述 +麗著稱 麗著稱 +麗著錄 麗著錄 +麗著書 麗著書 +連著作 連著作 +連著者 連著者 +連著名 連著名 +連著述 連著述 +連著稱 連著稱 +連著錄 連著錄 +連著書 連著書 +戀著作 戀著作 +戀著者 戀著者 +戀著名 戀著名 +戀著述 戀著述 +戀著稱 戀著稱 +戀著錄 戀著錄 +戀著書 戀著書 +涼著作 涼著作 +涼著者 涼著者 +涼著名 涼著名 +涼著述 涼著述 +涼著稱 涼著稱 +涼著錄 涼著錄 +涼著書 涼著書 +亮著作 亮著作 +亮著者 亮著者 +亮著名 亮著名 +亮著述 亮著述 +亮著稱 亮著稱 +亮著錄 亮著錄 +亮著書 亮著書 +臨著作 臨著作 +臨著者 臨著者 +臨著名 臨著名 +臨著述 臨著述 +臨著稱 臨著稱 +臨著錄 臨著錄 +臨著書 臨著書 +拎著作 拎著作 +拎著者 拎著者 +拎著名 拎著名 +拎著述 拎著述 +拎著稱 拎著稱 +拎著錄 拎著錄 +拎著書 拎著書 +領著作 領著作 +領著者 領著者 +領著名 領著名 +領著述 領著述 +領著稱 領著稱 +領著錄 領著錄 +領著書 領著書 +流著作 流著作 +流著者 流著者 +流著名 流著名 +流著述 流著述 +流著稱 流著稱 +流著錄 流著錄 +流著書 流著書 +留著作 留著作 +留著者 留著者 +留著名 留著名 +留著述 留著述 +留著稱 留著稱 +留著錄 留著錄 +留著書 留著書 +摟著作 摟著作 +摟著者 摟著者 +摟著名 摟著名 +摟著述 摟著述 +摟著稱 摟著稱 +摟著錄 摟著錄 +摟著書 摟著書 +陋著作 陋著作 +陋著者 陋著者 +陋著名 陋著名 +陋著述 陋著述 +陋著稱 陋著稱 +陋著錄 陋著錄 +陋著書 陋著書 +落著作 落著作 +落著者 落著者 +落著名 落著名 +落著述 落著述 +落著稱 落著稱 +落著錄 落著錄 +落著書 落著書 +罵著作 罵著作 +罵著者 罵著者 +罵著名 罵著名 +罵著述 罵著述 +罵著稱 罵著稱 +罵著錄 罵著錄 +罵著書 罵著書 +瞞著作 瞞著作 +瞞著者 瞞著者 +瞞著名 瞞著名 +瞞著述 瞞著述 +瞞著稱 瞞著稱 +瞞著錄 瞞著錄 +瞞著書 瞞著書 +漫著作 漫著作 +漫著者 漫著者 +漫著名 漫著名 +漫著述 漫著述 +漫著稱 漫著稱 +漫著錄 漫著錄 +漫著書 漫著書 +忙著作 忙著作 +忙著者 忙著者 +忙著名 忙著名 +忙著述 忙著述 +忙著稱 忙著稱 +忙著錄 忙著錄 +忙著書 忙著書 +冒著作 冒著作 +冒著者 冒著者 +冒著名 冒著名 +冒著述 冒著述 +冒著稱 冒著稱 +冒著錄 冒著錄 +冒著書 冒著書 +美著作 美著作 +美著者 美著者 +美著名 美著名 +美著述 美著述 +美著稱 美著稱 +美著錄 美著錄 +美著書 美著書 +夢著作 夢著作 +夢著者 夢著者 +夢著名 夢著名 +夢著述 夢著述 +夢著稱 夢著稱 +夢著錄 夢著錄 +夢著書 夢著書 +蒙著作 蒙著作 +蒙著者 蒙著者 +蒙著名 蒙著名 +蒙著述 蒙著述 +蒙著稱 蒙著稱 +蒙著錄 蒙著錄 +蒙著書 蒙著書 +拿著作 拿著作 +拿著者 拿著者 +拿著名 拿著名 +拿著述 拿著述 +拿著稱 拿著稱 +拿著錄 拿著錄 +拿著書 拿著書 +逆著作 逆著作 +逆著者 逆著者 +逆著名 逆著名 +逆著述 逆著述 +逆著稱 逆著稱 +逆著錄 逆著錄 +逆著書 逆著書 +釀著作 釀著作 +釀著者 釀著者 +釀著名 釀著名 +釀著述 釀著述 +釀著稱 釀著稱 +釀著錄 釀著錄 +釀著書 釀著書 +努著作 努著作 +努著者 努著者 +努著名 努著名 +努著述 努著述 +努著稱 努著稱 +努著錄 努著錄 +努著書 努著書 +趴著作 趴著作 +趴著者 趴著者 +趴著名 趴著名 +趴著述 趴著述 +趴著稱 趴著稱 +趴著錄 趴著錄 +趴著書 趴著書 +跑著作 跑著作 +跑著者 跑著者 +跑著名 跑著名 +跑著述 跑著述 +跑著稱 跑著稱 +跑著錄 跑著錄 +跑著書 跑著書 +陪著作 陪著作 +陪著者 陪著者 +陪著名 陪著名 +陪著述 陪著述 +陪著稱 陪著稱 +陪著錄 陪著錄 +陪著書 陪著書 +配著作 配著作 +配著者 配著者 +配著名 配著名 +配著述 配著述 +配著稱 配著稱 +配著錄 配著錄 +配著書 配著書 +披著作 披著作 +披著者 披著者 +披著名 披著名 +披著述 披著述 +披著稱 披著稱 +披著錄 披著錄 +披著書 披著書 +騙著作 騙著作 +騙著者 騙著者 +騙著名 騙著名 +騙著述 騙著述 +騙著稱 騙著稱 +騙著錄 騙著錄 +騙著書 騙著書 +飄著作 飄著作 +飄著者 飄著者 +飄著名 飄著名 +飄著述 飄著述 +飄著稱 飄著稱 +飄著錄 飄著錄 +飄著書 飄著書 +拼著作 拼著作 +拼著者 拼著者 +拼著名 拼著名 +拼著述 拼著述 +拼著稱 拼著稱 +拼著錄 拼著錄 +拼著書 拼著書 +鋪著作 鋪著作 +鋪著者 鋪著者 +鋪著名 鋪著名 +鋪著述 鋪著述 +鋪著稱 鋪著稱 +鋪著錄 鋪著錄 +鋪著書 鋪著書 +騎著作 騎著作 +騎著者 騎著者 +騎著名 騎著名 +騎著述 騎著述 +騎著稱 騎著稱 +騎著錄 騎著錄 +騎著書 騎著書 +牽著作 牽著作 +牽著者 牽著者 +牽著名 牽著名 +牽著述 牽著述 +牽著稱 牽著稱 +牽著錄 牽著錄 +牽著書 牽著書 +求著作 求著作 +求著者 求著者 +求著名 求著名 +求著述 求著述 +求著稱 求著稱 +求著錄 求著錄 +求著書 求著書 +去著作 去著作 +去著者 去著者 +去著名 去著名 +去著述 去著述 +去著稱 去著稱 +去著錄 去著錄 +去著書 去著書 +嚷著作 嚷著作 +嚷著者 嚷著者 +嚷著名 嚷著名 +嚷著述 嚷著述 +嚷著稱 嚷著稱 +嚷著錄 嚷著錄 +嚷著書 嚷著書 +繞著作 繞著作 +繞著者 繞著者 +繞著名 繞著名 +繞著述 繞著述 +繞著稱 繞著稱 +繞著錄 繞著錄 +繞著書 繞著書 +忍著作 忍著作 +忍著者 忍著者 +忍著名 忍著名 +忍著述 忍著述 +忍著稱 忍著稱 +忍著錄 忍著錄 +忍著書 忍著書 +揉著作 揉著作 +揉著者 揉著者 +揉著名 揉著名 +揉著述 揉著述 +揉著稱 揉著稱 +揉著錄 揉著錄 +揉著書 揉著書 +潤著作 潤著作 +潤著者 潤著者 +潤著名 潤著名 +潤著述 潤著述 +潤著稱 潤著稱 +潤著錄 潤著錄 +潤著書 潤著書 +燒著作 燒著作 +燒著者 燒著者 +燒著名 燒著名 +燒著述 燒著述 +燒著稱 燒著稱 +燒著錄 燒著錄 +燒著書 燒著書 +身著作 身著作 +身著者 身著者 +身著名 身著名 +身著述 身著述 +身著稱 身著稱 +身著錄 身著錄 +身著書 身著書 +沉著作 沉著作 +沉著者 沉著者 +沉著名 沉著名 +沉著述 沉著述 +沉著稱 沉著稱 +沉著錄 沉著錄 +沉著書 沉著書 +盛著作 盛著作 +盛著者 盛著者 +盛著名 盛著名 +盛著述 盛著述 +盛著稱 盛著稱 +盛著錄 盛著錄 +盛著書 盛著書 +試著作 試著作 +試著者 試著者 +試著名 試著名 +試著述 試著述 +試著稱 試著稱 +試著錄 試著錄 +試著書 試著書 +守著作 守著作 +守著者 守著者 +守著名 守著名 +守著述 守著述 +守著稱 守著稱 +守著錄 守著錄 +守著書 守著書 +受著作 受著作 +受著者 受著者 +受著名 受著名 +受著述 受著述 +受著稱 受著稱 +受著錄 受著錄 +受著書 受著書 +梳著作 梳著作 +梳著者 梳著者 +梳著名 梳著名 +梳著述 梳著述 +梳著稱 梳著稱 +梳著錄 梳著錄 +梳著書 梳著書 +豎著作 豎著作 +豎著者 豎著者 +豎著名 豎著名 +豎著述 豎著述 +豎著稱 豎著稱 +豎著錄 豎著錄 +豎著書 豎著書 +數著作 數著作 +數著者 數著者 +數著名 數著名 +數著述 數著述 +數著稱 數著稱 +數著錄 數著錄 +數著書 數著書 +睡不著作 睡不著作 +睡不著者 睡不著者 +睡不著名 睡不著名 +睡不著述 睡不著述 +睡不著稱 睡不著稱 +睡不著錄 睡不著錄 +睡不著書 睡不著書 +睡著作 睡著作 +睡著者 睡著者 +睡著名 睡著名 +睡著述 睡著述 +睡著稱 睡著稱 +睡著錄 睡著錄 +睡著書 睡著書 +順著作 順著作 +順著者 順著者 +順著名 順著名 +順著述 順著述 +順著稱 順著稱 +順著錄 順著錄 +順著書 順著書 +隨著作 隨著作 +隨著者 隨著者 +隨著名 隨著名 +隨著述 隨著述 +隨著稱 隨著稱 +隨著錄 隨著錄 +隨著書 隨著書 +踏著作 踏著作 +踏著者 踏著者 +踏著名 踏著名 +踏著述 踏著述 +踏著稱 踏著稱 +踏著錄 踏著錄 +踏著書 踏著書 +抬著作 抬著作 +抬著者 抬著者 +抬著名 抬著名 +抬著述 抬著述 +抬著稱 抬著稱 +抬著錄 抬著錄 +抬著書 抬著書 +躺著作 躺著作 +躺著者 躺著者 +躺著名 躺著名 +躺著述 躺著述 +躺著稱 躺著稱 +躺著錄 躺著錄 +躺著書 躺著書 +提著作 提著作 +提著者 提著者 +提著名 提著名 +提著述 提著述 +提著稱 提著稱 +提著錄 提著錄 +提著書 提著書 +甜著作 甜著作 +甜著者 甜著者 +甜著名 甜著名 +甜著述 甜著述 +甜著稱 甜著稱 +甜著錄 甜著錄 +甜著書 甜著書 +挑著作 挑著作 +挑著者 挑著者 +挑著名 挑著名 +挑著述 挑著述 +挑著稱 挑著稱 +挑著錄 挑著錄 +挑著書 挑著書 +跳著作 跳著作 +跳著者 跳著者 +跳著名 跳著名 +跳著述 跳著述 +跳著稱 跳著稱 +跳著錄 跳著錄 +跳著書 跳著書 +聽著作 聽著作 +聽著者 聽著者 +聽著名 聽著名 +聽著述 聽著述 +聽著稱 聽著稱 +聽著錄 聽著錄 +聽著書 聽著書 +聽著作 聽著作 +聽著者 聽著者 +聽著名 聽著名 +聽著述 聽著述 +聽著稱 聽著稱 +聽著錄 聽著錄 +聽著書 聽著書 +偷著作 偷著作 +偷著者 偷著者 +偷著名 偷著名 +偷著述 偷著述 +偷著稱 偷著稱 +偷著錄 偷著錄 +偷著書 偷著書 +拖著作 拖著作 +拖著者 拖著者 +拖著名 拖著名 +拖著述 拖著述 +拖著稱 拖著稱 +拖著錄 拖著錄 +拖著書 拖著書 +望著作 望著作 +望著者 望著者 +望著名 望著名 +望著述 望著述 +望著稱 望著稱 +望著錄 望著錄 +望著書 望著書 +圍著作 圍著作 +圍著者 圍著者 +圍著名 圍著名 +圍著述 圍著述 +圍著稱 圍著稱 +圍著錄 圍著錄 +圍著書 圍著書 +味著作 味著作 +味著者 味著者 +味著名 味著名 +味著述 味著述 +味著稱 味著稱 +味著錄 味著錄 +味著書 味著書 +想著作 想著作 +想著者 想著者 +想著名 想著名 +想著述 想著述 +想著稱 想著稱 +想著錄 想著錄 +想著書 想著書 +響著作 響著作 +響著者 響著者 +響著名 響著名 +響著述 響著述 +響著稱 響著稱 +響著錄 響著錄 +響著書 響著書 +向著作 向著作 +向著者 向著者 +向著名 向著名 +向著述 向著述 +向著稱 向著稱 +向著錄 向著錄 +向著書 向著書 +笑著作 笑著作 +笑著者 笑著者 +笑著名 笑著名 +笑著述 笑著述 +笑著稱 笑著稱 +笑著錄 笑著錄 +笑著書 笑著書 +心著作 心著作 +心著者 心著者 +心著名 心著名 +心著述 心著述 +心著稱 心著稱 +心著錄 心著錄 +心著書 心著書 +信著作 信著作 +信著者 信著者 +信著名 信著名 +信著述 信著述 +信著稱 信著稱 +信著錄 信著錄 +信著書 信著書 +行著作 行著作 +行著者 行著者 +行著名 行著名 +行著述 行著述 +行著稱 行著稱 +行著錄 行著錄 +行著書 行著書 +性著作 性著作 +性著者 性著者 +性著名 性著名 +性著述 性著述 +性著稱 性著稱 +性著錄 性著錄 +性著書 性著書 +學著作 學著作 +學著者 學著者 +學著名 學著名 +學著述 學著述 +學著稱 學著稱 +學著錄 學著錄 +學著書 學著書 +尋著作 尋著作 +尋著者 尋著者 +尋著名 尋著名 +尋著述 尋著述 +尋著稱 尋著稱 +尋著錄 尋著錄 +尋著書 尋著書 +循著作 循著作 +循著者 循著者 +循著名 循著名 +循著述 循著述 +循著稱 循著稱 +循著錄 循著錄 +循著書 循著書 +壓著作 壓著作 +壓著者 壓著者 +壓著名 壓著名 +壓著述 壓著述 +壓著稱 壓著稱 +壓著錄 壓著錄 +壓著書 壓著書 +雅著作 雅著作 +雅著者 雅著者 +雅著名 雅著名 +雅著述 雅著述 +雅著稱 雅著稱 +雅著錄 雅著錄 +雅著書 雅著書 +沿著作 沿著作 +沿著者 沿著者 +沿著名 沿著名 +沿著述 沿著述 +沿著稱 沿著稱 +沿著錄 沿著錄 +沿著書 沿著書 +耀著作 耀著作 +耀著者 耀著者 +耀著名 耀著名 +耀著述 耀著述 +耀著稱 耀著稱 +耀著錄 耀著錄 +耀著書 耀著書 +掖著作 掖著作 +掖著者 掖著者 +掖著名 掖著名 +掖著述 掖著述 +掖著稱 掖著稱 +掖著錄 掖著錄 +掖著書 掖著書 +衣著作 衣著作 +衣著者 衣著者 +衣著名 衣著名 +衣著述 衣著述 +衣著稱 衣著稱 +衣著錄 衣著錄 +衣著書 衣著書 +疑著作 疑著作 +疑著者 疑著者 +疑著名 疑著名 +疑著述 疑著述 +疑著稱 疑著稱 +疑著錄 疑著錄 +疑著書 疑著書 +溢著作 溢著作 +溢著者 溢著者 +溢著名 溢著名 +溢著述 溢著述 +溢著稱 溢著稱 +溢著錄 溢著錄 +溢著書 溢著書 +藝著作 藝著作 +藝著者 藝著者 +藝著名 藝著名 +藝著述 藝著述 +藝著稱 藝著稱 +藝著錄 藝著錄 +藝著書 藝著書 +因著作 因著作 +因著者 因著者 +因著名 因著名 +因著述 因著述 +因著稱 因著稱 +因著錄 因著錄 +因著書 因著書 +印著作 印著作 +印著者 印著者 +印著名 印著名 +印著述 印著述 +印著稱 印著稱 +印著錄 印著錄 +印著書 印著書 +應著作 應著作 +應著者 應著者 +應著名 應著名 +應著述 應著述 +應著稱 應著稱 +應著錄 應著錄 +應著書 應著書 +映著作 映著作 +映著者 映著者 +映著名 映著名 +映著述 映著述 +映著稱 映著稱 +映著錄 映著錄 +映著書 映著書 +用不著作 用不著作 +用不著者 用不著者 +用不著名 用不著名 +用不著述 用不著述 +用不著稱 用不著稱 +用不著錄 用不著錄 +用不著書 用不著書 +用著作 用著作 +用著者 用著者 +用著名 用著名 +用著述 用著述 +用著稱 用著稱 +用著錄 用著錄 +用著書 用著書 +悠著作 悠著作 +悠著者 悠著者 +悠著名 悠著名 +悠著述 悠著述 +悠著稱 悠著稱 +悠著錄 悠著錄 +悠著書 悠著書 +有著作 有著作 +有著者 有著者 +有著名 有著名 +有著述 有著述 +有著稱 有著稱 +有著錄 有著錄 +有著書 有著書 +與著作 與著作 +與著者 與著者 +與著名 與著名 +與著述 與著述 +與著稱 與著稱 +與著錄 與著錄 +與著書 與著書 +語著作 語著作 +語著者 語著者 +語著名 語著名 +語著述 語著述 +語著稱 語著稱 +語著錄 語著錄 +語著書 語著書 +豫著作 豫著作 +豫著者 豫著者 +豫著名 豫著名 +豫著述 豫著述 +豫著稱 豫著稱 +豫著錄 豫著錄 +豫著書 豫著書 +遠著作 遠著作 +遠著者 遠著者 +遠著名 遠著名 +遠著述 遠著述 +遠著稱 遠著稱 +遠著錄 遠著錄 +遠著書 遠著書 +躍著作 躍著作 +躍著者 躍著者 +躍著名 躍著名 +躍著述 躍著述 +躍著稱 躍著稱 +躍著錄 躍著錄 +躍著書 躍著書 +雜著作 雜著作 +雜著者 雜著者 +雜著名 雜著名 +雜著述 雜著述 +雜著稱 雜著稱 +雜著錄 雜著錄 +雜著書 雜著書 +載著作 載著作 +載著者 載著者 +載著名 載著名 +載著述 載著述 +載著稱 載著稱 +載著錄 載著錄 +載著書 載著書 +在著作 在著作 +在著者 在著者 +在著名 在著名 +在著述 在著述 +在著稱 在著稱 +在著錄 在著錄 +在著書 在著書 +紮著作 紮著作 +紮著者 紮著者 +紮著名 紮著名 +紮著述 紮著述 +紮著稱 紮著稱 +紮著錄 紮著錄 +紮著書 紮著書 +展著作 展著作 +展著者 展著者 +展著名 展著名 +展著述 展著述 +展著稱 展著稱 +展著錄 展著錄 +展著書 展著書 +站著作 站著作 +站著者 站著者 +站著名 站著名 +站著述 站著述 +站著稱 站著稱 +站著錄 站著錄 +站著書 站著書 +戰著作 戰著作 +戰著者 戰著者 +戰著名 戰著名 +戰著述 戰著述 +戰著稱 戰著稱 +戰著錄 戰著錄 +戰著書 戰著書 +蘸著作 蘸著作 +蘸著者 蘸著者 +蘸著名 蘸著名 +蘸著述 蘸著述 +蘸著稱 蘸著稱 +蘸著錄 蘸著錄 +蘸著書 蘸著書 +仗著作 仗著作 +仗著者 仗著者 +仗著名 仗著名 +仗著述 仗著述 +仗著稱 仗著稱 +仗著錄 仗著錄 +仗著書 仗著書 +找不著作 找不著作 +找不著者 找不著者 +找不著名 找不著名 +找不著述 找不著述 +找不著稱 找不著稱 +找不著錄 找不著錄 +找不著書 找不著書 +照著作 照著作 +照著者 照著者 +照著名 照著名 +照著述 照著述 +照著稱 照著稱 +照著錄 照著錄 +照著書 照著書 +罩著作 罩著作 +罩著者 罩著者 +罩著名 罩著名 +罩著述 罩著述 +罩著稱 罩著稱 +罩著錄 罩著錄 +罩著書 罩著書 +貞著作 貞著作 +貞著者 貞著者 +貞著名 貞著名 +貞著述 貞著述 +貞著稱 貞著稱 +貞著錄 貞著錄 +貞著書 貞著書 +枕著作 枕著作 +枕著者 枕著者 +枕著名 枕著名 +枕著述 枕著述 +枕著稱 枕著稱 +枕著錄 枕著錄 +枕著書 枕著書 +爭著作 爭著作 +爭著者 爭著者 +爭著名 爭著名 +爭著述 爭著述 +爭著稱 爭著稱 +爭著錄 爭著錄 +爭著書 爭著書 +掙著作 掙著作 +掙著者 掙著者 +掙著名 掙著名 +掙著述 掙著述 +掙著稱 掙著稱 +掙著錄 掙著錄 +掙著書 掙著書 +制著作 制著作 +制著者 制著者 +制著名 制著名 +制著述 制著述 +制著稱 制著稱 +制著錄 制著錄 +制著書 制著書 +志著作 志著作 +志著者 志著者 +志著名 志著名 +志著述 志著述 +志著稱 志著稱 +志著錄 志著錄 +志著書 志著書 +皺著作 皺著作 +皺著者 皺著者 +皺著名 皺著名 +皺著述 皺著述 +皺著稱 皺著稱 +皺著錄 皺著錄 +皺著書 皺著書 +住著作 住著作 +住著者 住著者 +住著名 住著名 +住著述 住著述 +住著稱 住著稱 +住著錄 住著錄 +住著書 住著書 +抓著作 抓著作 +抓著者 抓著者 +抓著名 抓著名 +抓著述 抓著述 +抓著稱 抓著稱 +抓著錄 抓著錄 +抓著書 抓著書 +轉著作 轉著作 +轉著者 轉著者 +轉著名 轉著名 +轉著述 轉著述 +轉著稱 轉著稱 +轉著錄 轉著錄 +轉著書 轉著書 +裝著作 裝著作 +裝著者 裝著者 +裝著名 裝著名 +裝著述 裝著述 +裝著稱 裝著稱 +裝著錄 裝著錄 +裝著書 裝著書 +追著作 追著作 +追著者 追著者 +追著名 追著名 +追著述 追著述 +追著稱 追著稱 +追著錄 追著錄 +追著書 追著書 +髭著作 髭著作 +髭著者 髭著者 +髭著名 髭著名 +髭著述 髭著述 +髭著稱 髭著稱 +髭著錄 髭著錄 +髭著書 髭著書 +走著作 走著作 +走著者 走著者 +走著名 走著名 +走著述 走著述 +走著稱 走著稱 +走著錄 走著錄 +走著書 走著書 +坐著作 坐著作 +坐著者 坐著者 +坐著名 坐著名 +坐著述 坐著述 +坐著稱 坐著稱 +坐著錄 坐著錄 +坐著書 坐著書 +做著作 做著作 +做著者 做著者 +做著名 做著名 +做著述 做著述 +做著稱 做著稱 +做著錄 做著錄 +做著書 做著書 +含著作 含著作 +含著者 含著者 +含著名 含著名 +含著述 含著述 +含著稱 含著稱 +含著錄 含著錄 +含著書 含著書 +涵著作 涵著作 +涵著者 涵著者 +涵著名 涵著名 +涵著述 涵著述 +涵著稱 涵著稱 +涵著錄 涵著錄 +涵著書 涵著書 +演著作 演著作 +演著者 演著者 +演著名 演著名 +演著述 演著述 +演著稱 演著稱 +演著錄 演著錄 +演著書 演著書 +保障著作 保障著作 +保障著者 保障著者 +保障著名 保障著名 +保障著述 保障著述 +保障著稱 保障著稱 +保障著錄 保障著錄 +保障著書 保障著書 +黏著作 黏著作 +黏著者 黏著者 +黏著名 黏著名 +黏著述 黏著述 +黏著稱 黏著稱 +黏著錄 黏著錄 +黏著書 黏著書 +膠著作 膠著作 +膠著者 膠著者 +膠著名 膠著名 +膠著述 膠著述 +膠著稱 膠著稱 +膠著錄 膠著錄 +膠著書 膠著書 +附著作 附著作 +附著者 附著者 +附著名 附著名 +附著述 附著述 +附著稱 附著稱 +附著錄 附著錄 +附著書 附著書 +代表著作 代表著作 +代表著者 代表著者 +代表著名 代表著名 +代表著述 代表著述 +代表著稱 代表著稱 +代表著錄 代表著錄 +代表著書 代表著書 +浮著作 浮著作 +浮著者 浮著者 +浮著名 浮著名 +浮著述 浮著述 +浮著稱 浮著稱 +浮著錄 浮著錄 +浮著書 浮著書 +寫著作 寫著作 +寫著者 寫著者 +寫著名 寫著名 +寫著述 寫著述 +寫著稱 寫著稱 +寫著錄 寫著錄 +寫著書 寫著書 +遇著作 遇著作 +遇著者 遇著者 +遇著名 遇著名 +遇著述 遇著述 +遇著稱 遇著稱 +遇著錄 遇著錄 +遇著書 遇著書 +殺著作 殺著作 +殺著者 殺著者 +殺著名 殺著名 +殺著述 殺著述 +殺著稱 殺著稱 +殺著錄 殺著錄 +殺著書 殺著書 +新著龍虎門 新著龍虎門 +榴莲 榴槤 +榴蓮 榴槤 +发布 發佈 +發布 發佈
\ No newline at end of file diff --git a/includes/zhtable/toSG.manual b/includes/zhtable/toSG.manual index 3c0cbc1d..2d39aa35 100644 --- a/includes/zhtable/toSG.manual +++ b/includes/zhtable/toSG.manual @@ -17,3 +17,5 @@ 民乐 华乐 住房 住屋 房价 屋价 +榴莲 榴梿 +榴蓮 榴梿
\ No newline at end of file diff --git a/includes/zhtable/toSimp.manual b/includes/zhtable/toSimp.manual index d18fc8c7..c02ed00e 100644 --- a/includes/zhtable/toSimp.manual +++ b/includes/zhtable/toSimp.manual @@ -5,10 +5,49 @@ 乾纲 乾纲 乾红 乾红 乾清宫 乾清宫 +乾仪 乾仪 +乾兴 乾兴 +乾冈 乾冈 +乾刘 乾刘 +乾刚 乾刚 +乾启 乾启 +乾宁 乾宁 +乾岗 乾岗 +乾录 乾录 +乾晖 乾晖 +乾构 乾构 +乾枢 乾枢 +乾栋 乾栋 +乾灵 乾灵 +乾窦 乾窦 +乾笃 乾笃 +乾纽 乾纽 +乾络 乾络 +乾统 乾统 +乾维 乾维 +乾罗 乾罗 +乾荫 乾荫 +乾象历 乾象历 +乾贞 乾贞 +乾贶 乾贶 +乾车 乾车 +乾轴 乾轴 +乾鉴 乾鉴 +乾钧 乾钧 +乾闼 乾闼 +乾顾 乾顾 +乾风 乾风 +乾马 乾马 +乾鹄 乾鹄 +乾鹊 乾鹊 +乾龙 乾龙 +天道为乾 天道为乾 +易经·乾 易经·乾 +易经乾 易经乾 柳诒徵 柳诒徵 於夫罗 於夫罗 於梨华 於梨华 -于潜县 於潜县 +於潜县 於潜县 憑藉 凭借 藉端 借端 藉故 借故 @@ -19,3 +58,62 @@ 藉機 借机 藉此 借此 藉由 借由 +沈積 沉积 +沈船 沉船 +沈默 沉默 +沈沒 沉没 +彷彿 仿佛 +項鍊 项链 +肘手鍊足 肘手链足 +鍊子 链子 +鍊條 链条 +拉鍊 拉链 +鉸鍊 铰链 +鍊鎖 链锁 +鎖鍊 锁链 +鐵鍊 铁链 +金鍊 金链 +銀鍊 银链 +鍊錘 链锤 +洗鍊 洗练 +石碁镇 石碁镇 +反覆 反复 +回覆 回复 +答覆 答复 +反反覆覆 反反复复 +重覆 重复 +鬱姓 鬱姓 +鬱氏 鬱氏 +侏儸紀 侏罗纪 +夥計 伙计 +吳其濬 吴其濬 +吴其濬 吴其濬 +乾泉水 干泉水 +么半群 幺半群 +么元 幺元 +么爹 幺爹 +么叔 幺叔 +么舅 幺舅 +么爸 幺爸 +么媽 幺妈 +么姨 幺姨 +么娘 幺娘 +么孃 幺娘 +幺孃 幺娘 +么妹 幺妹 +么小 幺小 +么姓 幺姓 +么氏 幺氏 +么蛾子 幺蛾子 +幺厮 幺厮 +睪丸 睾丸 +附睪 附睾 +隱睪 隱睾 +麼麼 麽麽 +么麼 幺麽 +么麼小丑 幺麽小丑 +么鳳 幺凤 +么二三 幺二三 +么篇 幺篇 +么謙 幺谦 +麴义 麴义
\ No newline at end of file diff --git a/includes/zhtable/toTW.manual b/includes/zhtable/toTW.manual index b0041ccf..1cc527fa 100644 --- a/includes/zhtable/toTW.manual +++ b/includes/zhtable/toTW.manual @@ -4,19 +4,41 @@ ’ 』 着 著 元凶 元凶 +元兇 元凶 凶器 凶器 +兇器 凶器 凶徒 凶徒 +兇徒 凶徒 凶手 凶手 +兇手 凶手 凶案 凶案 +兇案 凶案 凶残 凶殘 +凶殘 凶殘 +兇殘 凶殘 凶杀 凶殺 +凶殺 凶殺 +兇殺 凶殺 疑凶 疑凶 +疑兇 疑凶 真凶 真凶 +真兇 真凶 缉凶 緝凶 +緝凶 緝凶 +緝兇 緝凶 行凶 行凶 +行兇 行凶 行凶后 行凶後 +行凶後 行凶後 +行兇後 行凶後 买凶 買凶 +買凶 買凶 +買兇 買凶 追凶 追凶 +追兇 追凶 +逞凶斗狠 逞凶鬥狠 +逞凶鬥狠 逞凶鬥狠 +逞兇鬥狠 逞凶鬥狠 复苏 復甦 復蘇 復甦 缺省 預設 @@ -98,6 +120,8 @@ 词汇 辭彙 习用 慣用 元音 母音 +新纪元 新紀元 +新紀元 新紀元 宋元 宋元 任意球 自由球 头球 頭槌 @@ -315,3 +339,19 @@ 凡高 梵谷 狄安娜 黛安娜 戴安娜 黛安娜 +颁布 頒布 +頒佈 頒布 +彩带 彩帶 +彩排 彩排 +彩楼 彩樓 +彩牌楼 彩牌樓 +彩球 綵球 +彩绸 綵綢 +彩线 綵線 +彩船 綵船 +彩衣 綵衣 +结彩 結綵 +戏彩娱亲 戲綵娛親 +剪彩 剪綵 +榴莲 榴槤 +榴蓮 榴槤
\ No newline at end of file diff --git a/includes/zhtable/toTrad.manual b/includes/zhtable/toTrad.manual index 76d0ab58..b392adc5 100644 --- a/includes/zhtable/toTrad.manual +++ b/includes/zhtable/toTrad.manual @@ -11,6 +11,8 @@ 周杰倫 周杰倫 寶曆 寶曆 涂謹申 涂謹申 +涂鴻欽 涂鴻欽 +涂壯勳 涂壯勳 於姓 於姓 於氏 於氏 於夫羅 於夫羅 @@ -40,3 +42,26 @@ 於夫罗 於夫羅 府干預 府干預 府干擾 府干擾 +分布圖 分布圖 +頁面 頁面 +面條目 面條目 +黃鈺筑 黃鈺筑 +仿佛 彷彿 +凶殘 兇殘 +凶殺 兇殺 +緝凶 緝兇 +行凶後 行兇後 +買凶 買兇 +逞凶鬥狠 逞兇鬥狠 +合著者 合著者 +答复 答覆 +反复 反覆 +索馬里 索馬里 +洗练 洗鍊 +朝乾夕惕 朝乾夕惕 +乾象曆 乾象曆 +乾象历 乾象曆 +不好干預 不好干預 +不干預 不干預 +不干擾 不干擾 +不干牠 不干牠
\ No newline at end of file diff --git a/includes/zhtable/trad2simp.manual b/includes/zhtable/trad2simp.manual index 458a3c92..d5bcfa0e 100644 --- a/includes/zhtable/trad2simp.manual +++ b/includes/zhtable/trad2simp.manual @@ -24,3 +24,256 @@ U+08b6d譭|U+06bc1毁| U+071ec燬|U+06bc1毁| U+08457著|U+08457著|U+07740着| U+05d11崑|U+06606昆| +U+06372捲|U+05377卷| +U+07D2E紮|U+0624E扎| +U+07C64籤|U+07B7E签| +U+05925夥|U+04F19伙| +U+05F46彆|U+0522B别| +U+09D70鵰|U+096D5雕|U+05F6B彫| +U+0617E慾|U+06B32欲| +U+07A1C稜|U+068F1棱| +U+06A11樑|U+06881梁| +U+04F54佔|U+05360占| +U+05016倖|U+05E78幸| +U+050A2傢|U+05BB6家| +U+050F1僱|U+096C7雇| +U+053A4厤|U+05386历| +U+05641噁|U+06076恶| +U+056CC囌|U+082CF苏| +U+05D19崙|U+04ED1仑| +U+05F14弔|U+0540A吊| +U+061DE懞|U+08499蒙| +U+062DA拚|U+062FC拼| +U+0647A摺|U+06298折| +U+06607昇|U+05347升| +U+0672E朮|U+0672F术| +U+069A6榦|U+05E72干| +U+06E67湧|U+06D8C涌| +U+06ED9滙|U+06C47汇| +U+06F90澐|U+06C84沄| +U+06FDB濛|U+08499蒙| +U+07030瀰|U+05F25弥| +U+07526甦|U+082CF苏| +U+07575畵|U+0753B画|U+05212划| +U+0756B畫|U+0753B画|U+05212划| +U+076C3盃|U+0676F杯| +U+077AD瞭|U+04E86了| +U+077C7矇|U+08499蒙| +U+07843硃|U+06731朱| +U+07C72籲|U+05401吁| +U+07CF0糰|U+056E2团| +U+07E34縴|U+07EA4纤| +U+07E94纔|U+0624D才| +U+08591薑|U+059DC姜| +U+0884A衊|U+08511蔑| +U+08A17託|U+06258托|U+08BAC讬| +U+08B8E讎|U+04EC7仇| +U+08B9A讚|U+08D5E赞| +U+08FF4迴|U+056DE回| +U+0955F镟|U+065CB旋| +U+095A4閤|U+05408合| +U+0965E陞|U+05347升| +U+097A6鞦|U+079CB秋|U+097A7鞧| +U+097C6韆|U+05343千| +U+09935餵|U+05582喂| +U+09B28鬨|U+054C4哄| +U+09EAA麪|U+09762面| +U+09EAB麫|U+09762面| +U+09EAF麯|U+066F2曲| +U+09EF4黴|U+09709霉| +U+09F15鼕|U+051AC冬| +U+09F63齣|U+051FA出| +U+068E1棡|U+0E82D| +U+08C54豔|U+08273艳| +U+06B4E歎|U+053F9叹| +U+0938C鎌|U+09570镰| +U+07515甕|U+074EE瓮| +U+07652癒|U+06108愈| +U+069D3槓|U+06760杠| +U+06D29洩|U+06CC4泄| +U+09451鑑|U+09274鉴| +U+08AEE諮|U+054A8咨|U+08C18谘| +U+052F3勳|U+052CB勋| +U+06BAD殭|U+050F5僵| +U+09C47鱇|U+29F8C𩾌| +U+03473㑳|U+03447㑇| +U+09E7C鹼|U+078B1碱|U+07877硷| +U+0962A阪|U+0962A阪|U+05742坂| +U+0934A鍊|U+070BC炼|U+094FE链| +U+08986覆|U+08986覆|U+0590D复| +U+085C9藉|U+085C9藉|U+0501F借| +U+05138儸|U+03469㑩|U+07F57罗| +U+06FDB濛|U+06FDB濛|U+08499蒙| +U+07B87箇|U+04E2A个| +U+05277剷|U+094F2铲| +U+05690嚐|U+05C1D尝| +U+055AB喫|U+05403吃| +U+07661癡|U+075F4痴| +U+083F4菴|U+05EB5庵| +U+07DB5綵|U+05F69彩|U+0433D䌽| +U+08123脣|U+05507唇| +U+055F0嗰|U+20BB6𠮶| +U+04A8F䪏|U+293FC𩏼| +U+04A97䪗|U+29400𩐀| +U+04A98䪘|U+293FF𩏿| +U+04AF4䫴|U+29597𩖗| +U+04B18䬘|U+2966E𩙮| +U+04B1D䬝|U+2966F𩙯| +U+04B40䭀|U+29807𩠇| +U+04B43䭃|U+29808𩠈| +U+04B7F䭿|U+299ED𩧭| +U+04B9D䮝|U+299F0𩧰| +U+04B9E䮞|U+29A01𩨁| +U+04BA0䮠|U+299FF𩧿| +U+04BB3䮳|U+29A0F𩨏| +U+04BBE䮾|U+299EA𩧪| +U+04C59䱙|U+29F88𩾈| +U+04C6C䱬|U+29F8A𩾊| +U+04C70䱰|U+29F8B𩾋| +U+04C77䱷|U+04CA3䲣| +U+04CB0䲰|U+2A242𪉂| +U+04D2C䴬|U+2A388𪎈| +U+04D34䴴|U+2A38B𪎋| +U+091F3釳|U+28C3F𨰿| +U+091FE釾|U+0497A䥺| +U+0920B鈋|U+28C42𨱂| +U+09220鈠|U+28C41𨱁| +U+0922F鈯|U+28C44𨱄| +U+09232鈲|U+28C43𨱃| +U+09241鉁|U+28C45𨱅| +U+092B6銶|U+28C47𨱇| +U+092C9鋉|U+28C48𨱈| +U+09302錂|U+28C4B𨱋| +U+09344鍄|U+28C49𨱉| +U+0936E鍮|U+28C4E𨱎| +U+0939D鎝|U+28C4F𨱏| +U+093AF鎯|U+28C4D𨱍| +U+093B7鎷|U+28C3E𨰾| +U+093C6鏆|U+28C4C𨱌| +U+093C9鏉|U+28C52𨱒| +U+093FA鏺|U+0497D䥽| +U+0940E鐎|U+28C53𨱓| +U+0940F鐏|U+28C54𨱔| +U+09425鐥|U+04985䦅| +U+0942F鐯|U+04983䦃| +U+0958D閍|U+28E02𨸂| +U+09590閐|U+28E03𨸃| +U+09843顃|U+29596𩖖| +U+098B0颰|U+29665𩙥| +U+098B7颷|U+2966A𩙪| +U+098BE颾|U+2966B𩙫| +U+09938餸|U+2980C𩠌| +U+099CE駎|U+299E8𩧨| +U+099DA駚|U+299EB𩧫| +U+099E7駧|U+299F2𩧲| +U+099E9駩|U+299F4𩧴| +U+099F6駶|U+299FA𩧺| +U+09A14騔|U+29A00𩨀| +U+09A1A騚|U+29A0A𩨊| +U+09A1D騝|U+29A03𩨃| +U+09A1F騟|U+29A08𩨈| +U+09A2A騪|U+29A04𩨄| +U+09A4B驋|U+299EF𩧯| +U+09B65魥|U+29F79𩽹| +U+09B95鮕|U+29F80𩾀| +U+09B9F鮟|U+29F7E𩽾| +U+09BA3鮣|U+04C9F䲟| +U+09BB8鮸|U+29F83𩾃| +U+09BC4鯄|U+29F81𩾁| +U+09BF1鯱|U+29F87𩾇| +U+09BF6鯶|U+29F7C𩽼| +U+09C06鰆|U+04CA0䲠| +U+09C0C鰌|U+04CA1䲡| +U+09C27鰧|U+04CA2䲢| +U+09C47鱇|U+29F8C𩾌| +U+09CFC鳼|U+2A243𪉃| +U+09D1C鴜|U+2A248𪉈| +U+09D32鴲|U+2A246𪉆| +U+09D5A鵚|U+2A24D𪉍| +U+09DD4鷔|U+2A251𪉑| +U+09DE8鷨|U+2A24A𪉊| +U+09EA8麨|U+2A38A𪎊| +U+09EB2麲|U+2A389𪎉| +U+09EB3麳|U+2A38C𪎌| +U+2895B𨥛|U+28C40𨱀| +U+289F1𨧱|U+28C4A𨱊| +U+28AD2𨫒|U+28C50𨱐| +U+28B82𨮂|U+28C55𨱕| +U+293A2𩎢|U+293FE𩏾| +U+293EA𩏪|U+293FD𩏽| +U+294E3𩓣|U+29595𩖕| +U+295C0𩗀|U+29666𩙦| +U+295E1𩗡|U+29667𩙧| +U+29600𩘀|U+29669𩙩| +U+2961D𩘝|U+2966D𩙭| +U+29639𩘹|U+29668𩙨| +U+2963A𩘺|U+2966C𩙬| +U+29648𩙈|U+29670𩙰| +U+29726𩜦|U+29806𩠆| +U+29754𩝔|U+2980B𩠋| +U+297AF𩞯|U+04B6A䭪| +U+297D0𩟐|U+29805𩠅| +U+2987A𩡺|U+299E6𩧦| +U+298A1𩢡|U+299EC𩧬| +U+298B4𩢴|U+299F5𩧵| +U+298B8𩢸|U+299F3𩧳| +U+298BE𩢾|U+299EE𩧮| +U+298CF𩣏|U+299F6𩧶| +U+298F5𩣵|U+299FB𩧻| +U+298FA𩣺|U+299FC𩧼| +U+2990A𩤊|U+299E9𩧩| +U+29919𩤙|U+29A06𩨆| +U+29932𩤲|U+29A09𩨉| +U+29938𩤸|U+29A05𩨅| +U+29944𩥄|U+29A0B𩨋| +U+29947𩥇|U+29A0D𩨍| +U+29949𩥉|U+299F1𩧱| +U+29951𩥑|U+29A0C𩨌| +U+299C6𩧆|U+29A10𩨐| +U+29D69𩵩|U+29F7A𩽺| +U+29D79𩵹|U+29F7B𩽻| +U+29DB0𩶰|U+29F7F𩽿| +U+29DB1𩶱|U+29F7D𩽽| +U+29DF0𩷰|U+29F84𩾄| +U+29E03𩸃|U+29F85𩾅| +U+29E26𩸦|U+29F86𩾆| +U+29F47𩽇|U+29F8E𩾎| +U+29FEA𩿪|U+2A244𪉄| +U+2A026𪀦|U+2A245𪉅| +U+2A03E𪀾|U+2A24B𪉋| +U+2A048𪁈|U+2A249𪉉| +U+2A056𪁖|U+2A24C𪉌| +U+2A086𪂆|U+2A24E𪉎| +U+2A0CD𪃍|U+2A250𪉐| +U+2A0CF𪃏|U+2A24F𪉏| +U+2A106𪄆|U+2A254𪉔| +U+2A115𪄕|U+2A252𪉒| +U+2A1F3𪇳|U+2A255𪉕| +U+2A600𪘀|U+2A68F𪚏| +U+2A62F𪘯|U+2A690𪚐| +U+0617C慼|U+0621A戚| +U+093DA鏚|U+0621A戚| +U+07895碕|U+057FC埼| +U+068CA棊|U+068CB棋| +U+065C2旂|U+065D7旗| +U+06371捱|U+06328挨| +U+09061遡|U+06EAF溯| +U+06CDD泝|U+06EAF溯| +U+075E0痠|U+09178酸| +U+07958祘|U+07B97算| +U+05D57嵗|U+05C81岁| +U+21ED5𡻕|U+05C81岁| +U+07E50繐|U+07A57穗| +U+098F1飱|U+098E7飧| +U+06331挱|U+06332挲| +U+07C11簑|U+084D1蓑| +U+04308䌈|U+26216𦈖| +U+06FBE澾|U+03CE0㳠| +U+26A99𦪙|U+0447D䑽| +U+07F4E罎|U+0575B坛| +U+058DC壜|U+0575B坛| +U+058B0墰|U+0575B坛| +U+091A3醣|U+07CD6糖| +U+0537D卽|U+05373即| +U+065E3旣|U+065E2既| +U+09EB4麴|U+066F2曲|U+09EB4麴|
\ No newline at end of file diff --git a/includes/zhtable/trad2simp_supp_set.manual b/includes/zhtable/trad2simp_supp_set.manual index 6e6ed8ca..d1728f0a 100644 --- a/includes/zhtable/trad2simp_supp_set.manual +++ b/includes/zhtable/trad2simp_supp_set.manual @@ -1 +1,3 @@ 著 着 +藉 借 +濛 蒙
\ No newline at end of file diff --git a/includes/zhtable/tradphrases.manual b/includes/zhtable/tradphrases.manual index 02d07d20..4e9f7498 100644 --- a/includes/zhtable/tradphrases.manual +++ b/includes/zhtable/tradphrases.manual @@ -1,4 +1,5 @@ 零隻 +〇隻 一隻 二隻 兩隻 @@ -9,14 +10,36 @@ 七隻 八隻 九隻 -十隻 +0隻 +1隻 +2隻 +3隻 +4隻 +5隻 +6隻 +7隻 +8隻 +9隻 +0隻 +1隻 +2隻 +3隻 +4隻 +5隻 +6隻 +7隻 +8隻 +9隻 百隻 千隻 萬隻 億隻 多只是 +多只需 +最多只 多隻 0多隻 +0多隻 零多隻 十多隻 百多隻 @@ -71,7 +94,7 @@ 魚乾 乾梅 糕乾 -黃乾黑廋 +黃乾黑瘦 馬乾 香乾 趲幹 @@ -93,7 +116,6 @@ 沒乾沒淨 枝不得大於榦 杯乾 -朝乾夕惕 打幹 打乾噦 徐幹 @@ -181,8 +203,6 @@ 唇乾 單幹 勾幹 -不幹性油 -不幹不淨 豆乾 果乾 如果幹 @@ -197,7 +217,6 @@ 幹的停當 乾巴 偎乾 -借著 偷雞不著 几絲 划著 @@ -219,7 +238,6 @@ 參閱 吃著不盡 合著 -合著者 吊帶褲 吊掛著 吊著 @@ -240,6 +258,7 @@ 彆著 怎麼著 憑藉著 +憑藉 接著說 擔著 擔負著 @@ -308,6 +327,7 @@ 豎著 豐濱 豐濱鄉 +豐度 象徵著 這麼著 那麼著 @@ -371,15 +391,16 @@ 併當 併火 併肩子 -併兼 併除 併疊 忙併 打併 +簡併 並發表 並發現 並發展 並發動 +火並非 舉手表 揮手表 併一不二 @@ -523,7 +544,6 @@ 醜話 醜媳 醜吒 -醜生 醜聲遠播 醜夷 弄醜 @@ -533,8 +553,50 @@ 不嫌母醜 一爭兩醜 惡直醜正 +很醜 +醜男 +醜斃了 +醜奴兒 +醜言 +醜徒 +醜雜 +醜儕 +醜沮 +醜辭 +醜比 +醜辱 +醜逆 +醜史 +醜賊生 +醜婆子 +出乖弄醜 +出乖露醜 +獲匪其醜 +乙丑 +丁丑 +己丑 +辛丑 +癸丑 +丑時 +丑日 +丑月 +丑年 +文丑 +武丑 +女丑 +小丑 +大丑 +丑婆子 +丑旦 +丑角 +丑三 +丑表功 +公孫丑 么麼小丑 齣電影 +齣電視 +齣動畫 +齣節目 齣卡通 齣戲 齣劇 @@ -561,7 +623,6 @@ 抿髮 髮漂 髮匪 -髮光可鑒 髮腳 髮癬 髮釵 @@ -628,14 +689,7 @@ 閫範 雅範 霽範 -顏範 鴻範 -道範 -壼範 -霽範 -容範 -懿範 -樣範 沒樣範 丰采 丰標不凡 @@ -653,6 +707,7 @@ 複輔音 複元音 複平面 +複函數 複流 反複製 顛覆 @@ -743,23 +798,12 @@ 咬薑呷醋 薑蓉 薑黃 -駘藉 -躪藉 -凌藉 -顧藉 狐藉虎威 滑藉 -藉槁 藉寇兵 -藉卉 藉箸代籌 藉手 -藉甚 -藉草枕塊 -枕經藉史 -死傷相藉 -素藉 -茵藉 +藉此 龍捲 捲舌 夸父 @@ -832,6 +876,7 @@ 皇極曆 儒略改革曆 希伯來曆 +格里曆 厤物之意 爰定祥厤 白黴 @@ -910,12 +955,14 @@ 意大利麵 湯下麵 茶麵 +麵糰 冷面相 糞穢衊面 湟潦生苹 食野之苹 苹縈 青苹 +青蘋果 僕僕 有僕 冉有僕 @@ -944,7 +991,6 @@ 樸鄙 樸馬 樸父 -樸訥 樸陋 樸魯 樸厚 @@ -952,7 +998,6 @@ 樸質 樸拙 樸重 -樸實 樸素 樸樕 樸野 @@ -966,7 +1011,6 @@ 拙樸 斫雕為樸 斲雕為樸 -斲雕為樸 質樸 誠樸 純樸 @@ -1108,7 +1152,7 @@ 愿而恭 許愿起經 北嶽 -嶽麓山 +嶽麓 但云 胡云 詩云 @@ -1141,6 +1185,7 @@ 厂部 閤府 分佈 +佈道 剪綵 衝量 韶山衝 @@ -1160,7 +1205,6 @@ 拭乾 擦乾 晾乾 -枯乾 烘乾 肉乾 菜乾 @@ -1253,12 +1297,14 @@ 傷痕纍纍 儒略曆 伊斯蘭曆 -寶曆 酒麴 昇平 爾冬陞 澹臺 涂謹申 +涂鴻欽 +涂壯勳 +涂醒哲 拜託 委託 輓曲 @@ -1285,6 +1331,37 @@ 叶恭弘 叶 恭弘 叶 恭弘 +於1 +於2 +於3 +於4 +於5 +於6 +於7 +於8 +於9 +於0 +於1 +於2 +於3 +於4 +於5 +於6 +於7 +於8 +於9 +於0 +於一 +於二 +於三 +於四 +於五 +於六 +於七 +於八 +於九 +於十 +於半 於夫羅 於梨華 置於 @@ -1355,6 +1432,7 @@ 染指於 於火 存十一於千百 +存於 於勤 隱於 藏於 @@ -1435,7 +1513,6 @@ 耿於 於懷 服於 -致於 臻於 匿於 因於 @@ -1464,8 +1541,251 @@ 百紮 千紮 萬紮 +佔1 +佔2 +佔3 +佔4 +佔5 +佔6 +佔7 +佔8 +佔9 +佔0 +佔1 +佔2 +佔3 +佔4 +佔5 +佔6 +佔7 +佔8 +佔9 +佔0 +佔零 +佔〇 +佔一 +佔二 +佔兩 +佔三 +佔四 +佔五 +佔六 +佔七 +佔八 +佔九 +佔十 +佔百 +佔千 +佔万 +佔億 佔超過 +佔不足 +佔至少 +佔少 +佔至多 +佔半 +佔多 +佔大 +佔小 +佔中 +佔東 +佔西 +佔南 +佔北 +佔平均 +佔總 獨佔鰲頭 +所佔 +市佔 +佔率 +市佔率 +佔市場 +佔世界 +佔全 +佔國內 +佔美 +佔台 +佔香 +佔澳 +佔加 +佔新 +佔馬 +佔印 +佔英 +佔法 +佔德 +佔葡 +佔俄 +佔蘇 +佔缺 +佔A +佔B +佔C +佔D +佔E +佔F +佔G +佔H +佔I +佔J +佔K +佔L +佔M +佔N +佔O +佔P +佔Q +佔R +佔S +佔T +佔U +佔V +佔W +佔X +佔Y +佔Z +佔a +佔b +佔c +佔d +佔e +佔f +佔g +佔h +佔i +佔j +佔k +佔l +佔m +佔n +佔o +佔p +佔q +佔r +佔s +佔t +佔u +佔v +佔w +佔x +佔y +佔z +佔A +佔B +佔C +佔D +佔E +佔F +佔G +佔H +佔I +佔J +佔K +佔L +佔M +佔N +佔O +佔P +佔Q +佔R +佔S +佔T +佔U +佔V +佔W +佔X +佔Y +佔Z +佔a +佔b +佔c +佔d +佔e +佔f +佔g +佔h +佔i +佔j +佔k +佔l +佔m +佔n +佔o +佔p +佔q +佔r +佔s +佔t +佔u +佔v +佔w +佔x +佔y +佔z +佔不佔 +不佔 +佔了 +佔穩 +佔資源 +佔人便宜 +佔頭 +佔道 +佔屋 +佔網 +佔床 +佔座 +佔分 +佔飯 +佔個位 +佔後 +佔著 +佔山 +佔比 +佔停車 +佔哺乳 +佔下風 +少佔 +多佔 +費佔 +佔查 +佔壓 +佔優 +佔劣 +穩佔 +佔整體 +佔局部 +日佔 +美佔 +英佔 +德佔 +法佔 +俄佔 +葡佔 +西佔 +奧佔 +意佔 +義佔 +地佔 +佔場 +佔耕 +狂佔 +徵佔 +圈佔 +已佔 +佔囁 +佔主 +佔次 +寡佔 +佔去 +將佔 +將占卜 +要佔 +要占卜 +會佔 +會占卜 +占卜 +夢有五不占 +占有五不驗 誌異 筑前 筑後 @@ -1518,7 +1838,6 @@ 鐘乳洞 鐘乳石 鐘在寺里 -擊鐘 詩鐘 懸鐘 山崩鐘應 @@ -1528,7 +1847,6 @@ 二缶鐘惑 一口鐘 叩鐘 -盜鐘 音聲如鐘 應鐘 原子鐘 @@ -1554,6 +1872,8 @@ 屋樑 樑柱 柱樑 +下樑 +上梁山 昇陽 僥倖 夏遊 @@ -1666,3 +1986,1018 @@ 崑劇 崑蘇 蘇崑 +分布圖 +一干家中 +星期後 +不准你 +不准我 +不准他 +不准她 +不准它 +不准誰 +不准許 +准不准你 +准不准我 +准不准他 +准不准她 +准不准它 +准不准誰 +准不准許 +依依不捨 +戀戀不捨 +窮追不捨 +緊追不捨 +鍥而不捨 +稜登 +前言不答後語 +繃扒弔拷 +不弔 +不通弔慶 +陪弔 +盆弔 +屁股大弔了心 +撇弔 +憑弔 +門弔兒 +伐罪弔民 +打出弔入 +搗鬼弔白 +弔膀子 +弔民 +弔民伐罪 +弔奠 +弔頭 +弔古 +弔古尋幽 +弔詭 +弔詭矜奇 +弔客 +弔拷 +弔拷繃扒 +弔扣 +弔賀迎送 +弔鶴 +弔喉 +弔謊 +弔祭 +弔腳兒事 +弔頸 +弔橋 +弔取 +弔孝 +弔紙 +弔者大悅 +弔場 +弔書 +弔詞 +弔死 +弔死問疾 +弔撒 +弔喪 +弔喪問疾 +弔腰撒跨 +弔唁 +弔宴 +弔喭 +弔影 +弔慰 +弔文 +弔問 +頭巾弔在水裡 +提心弔膽 +弄鬼弔猴 +管人弔腳兒事 +開弔 +鶴弔 +昊天不弔 +花馬弔嘴 +會弔 +吉凶慶弔 +蟣蝨相弔 +祭弔 +祭弔文 +青蠅弔客 +慶弔 +形影相弔 +上弔 +哀弔 +一弔 +唁弔 +於水 +安於 +迫於 +罷於 +蹪於 +於敝 +於過 +甚於 +等於 +定於 +利於 +對於 +推舟於陸 +退藏於密 +歸於 +難於 +移禍於 +生於 +立於 +多於 +勝於 +傳於 +流於 +過於 +關於 +毀於 +基於 +急於 +嫁禍於 +借聽於聾 +見於 +鑒於 +謹於心 +求道於盲 +始於 +於藍 +出於 +輕於 +行百里者半於九十 +幸於 +怠於 +詢於芻蕘 +止於 +至於 +拙於 +忠於 +終於 +重於 +垂於 +善於 +死於 +屬於 +浮於 +在於 +厝薪於火 +易於 +精於 +由於 +於此 +燕巢於幕 +於菟 +於乎 +於戲 +於邑 +補於 +位於 +於今 +於是 +於是乎 +於斯 +寓於 +月離於畢 +月麗於箕 +源於 +且於 +長於 +短於 +現於 +較於 +於之 +分佈於 +分散於 +鬼谷子 +于美人 +緊緻 +冗餘 +曰云 +若干 +徵婚 +鬥鬨 +事有鬥巧 +歹鬥 +鬥茶 +鬥鴨 +爭奇鬥妍 +誇能鬥智 +春香鬥學 +鬥引 +鬥彩 +鬥武 +鬥悶 +鬥牙拌齒 +鬥幌子 +鬥腳 +雞吵鵝鬥 +辯鬥 +廝鬥 +誇多鬥靡 +臨潼鬥寶 +鬥趣 +撩鬥 +傲霜鬥雪 +賭鬥 +搬鬥 +鬥爭鬥合 +鬥疊 +鬥文 +耍鬥 +鬥巧 +油鬥 +蚊動牛鬥 +卵與石鬥 +挑鬥 +爭奇鬥異 +鬥葉子 +鬥分子 +爭妍鬥奇 +不鬥 +鬥心眼 +鬥頭 +挌鬥 +好鬥 +鬥合 +拚鬥 +兩虎共鬥 +兩鼠鬥穴 +鬥犀臺 +鬥牙鬥齒 +惡鬥 +鬥勝 +鬥富 +鬥艦 +鬥葉兒 +鬥彆氣 +鬥話 +鬥牌 +鬥百草 +鬥打 +鬥犬 +鬥風 +鬥雪紅 +鬥暴 +鬥閑氣 +龍鬥虎傷 +殷師牛鬥 +二虎相鬥 +鬥力 +爭紅鬥紫 +鬥麗 +鬥狠 +鬥飣 +虎鬥 +引鬥 +爭妍鬥豔 +轉鬥千里 +鬥而鑄兵 +困鬥 +好勇鬥狠 +爭奇鬥豔 +石樑 +木樑 +藏歷史 +頁面 +方面 +表面 +面條目 +課餘 +節餘 +盈餘 +病餘 +餘地 +餘力 +餘子 +餘事 +扶餘國 +腐餘 +富餘 +之餘 +餘澤 +流風餘俗 +流風餘韻 +淋餘土 +餘一 +餘二 +餘三 +餘四 +餘五 +餘六 +餘七 +餘八 +餘九 +餘十 +零餘 +〇餘 +餘零 +餘〇 +餘1 +餘2 +餘3 +餘4 +餘5 +餘6 +餘7 +餘8 +餘9 +餘0 +0餘 +餘1 +餘2 +餘3 +餘4 +餘5 +餘6 +餘7 +餘8 +餘9 +餘0 +0餘 +餘數 +其餘 +尸居餘氣 +賸餘 +餘孽 +殘餘 +業餘 +餘割 +餘款 +餘角 +餘切 +餘霞 +餘下 +餘弦 +餘震 +餘貾 +餘額 +禹餘糧 +餘人 +編余 +病余 +餘俗 +餘倍 +大讚 +唄讚 +褒讚 +謬讚 +誄讚 +祝讚 +詩讚 +賞讚 +讚唄 +飛紮 +紮裹 +紮腳 +紮詐 +紮囮 +住紮 +佔畢 +佔頭籌 +佔高枝兒 +隱佔 +憑摺 +沒摺至 +大摺兒 +大週摺 +火摺子 +裝摺 +變徵 +談徵 +納徵 +流徵 +柳詒徵 +固徵 +貴徵 +考徵 +咎徵 +杞宋無徵 +休徵 +徵辟 +徵名責實 +徵發 +徵風召雨 +徵答 +徵啟 +徵選 +徵招 +徵士 +徵庸 +之徵 +瑞徵 +三徵七辟 +額徵 +有徵 +無徵不信 +文徵明 +徵跡 +徵車 +徵效 +徵怪 +徵聖 +徵咎 +徵吏 +徵令 +本徵 +船鐘 +黃鈺筑 +齊莊 +鴻案相莊 +項莊 +韋莊 +鍋莊 +鄭莊公 +通莊 +蒙莊 +端莊 +票莊 +矜莊 +楚莊問鼎 +楚莊絕纓 +整莊 +打路莊板 +莊騷 +莊語 +莊舄越吟 +莊房 +莊客 +莊農 +平泉莊 +布莊 +香山庄 +寶莊 +坐莊 +周莊王 +發莊 +卞莊 +包莊 +剔莊貨 +劉克莊 +冷莊子 +石家莊 +卞莊子 +新莊市 +當準 +憑準 +沒準 +蜂準 +推情準理 +寇準 +合準 +準保 +準譜 +準分子 +準點 +一個準 +準擬 +準貨幣 +準式 +認準 +三準 +鵝準 +有準 +崑崙 +鎌倉 +請君入甕 +甕安 +痊癒 +槓桿 +宣洩 +圖鑑 +諮詢 +勳章 +張勳 +殭屍 +有栖川 +兇惡 +兇狠 +兇猛 +兇橫 +兇悍 +兇險 +兇相 +兇犯 +嫌兇 +兇嫌 +兇疑 +兇刀 +兇槍 +很兇 +兇巴巴 +行兇前 +凝鍊 +鍊貧 +鍊度 +鍊金 +鍊形 +鍊師 +鍊石 +鍊字 +鍊冶 +細鍊 +陳鍊 +闖鍊 +鍊汞 +淬鍊 +索馬里 +范登堡 +世田谷 +製漿 +三統歷史 +伊斯蘭教歷史 +伊斯蘭歷史 +儒略改革歷史 +儒略歷史 +公歷史 +台歷史 +合歷史 +周歷史 +商歷史 +四分歷史 +回歷史 +埃及歷史 +大明歷史 +大歷史 +大衍歷史 +太初歷史 +官歷史 +寶歷史 +巧歷史 +希伯來歷史 +弘歷史 +慶歷史 +日歷史 +星歷史 +月歷史 +朱理安歷史 +桌歷史 +永歷史 +玉歷史 +百花歷史 +皇歷史 +皇極歷史 +穆罕默德歷史 +算歷史 +紀歷史 +舊歷史 +航海歷史 +萬歷史 +行事歷史 +農歷史 +農民歷史 +通歷史 +長歷史 +陰歷史 +陽歷史 +額我略歷史 +黃歷史 +美醜 +獻醜 +出醜 +家醜 +遮醜 +醜八怪 +醜名 +醜詆 +醜態 +醜女 +醜類 +醜陋 +醜虜 +醜化 +醜劇 +醜媳婦 +醜小鴨 +醜行 +醜事 +醜聲 +醜人 +醜惡 +醜丫頭 +醜聞 +醜語 +母醜 +齣子 +齣兒 +賣獃 +痴獃 +發獃 +大獃 +獃獃 +獃等 +獃頭 +獃腦 +獃根 +獃磕 +獃憨獃 +獃話 +獃氣 +獃想 +獃性 +獃滯 +獃著 +獃痴 +獃串了皮 +獃事 +獃人 +獃子 +好獃 +占便宜的是獃 +阿獃 +丰標 +丰姿 +丰韻 +鵰翎 +鵰心雁爪 +鵰鶚 +雙鵰 +撲鼕鼕 +普鼕鼕 +鼕鼕鼓 +被髮入山 +被髮左衽 +被髮佯狂 +被髮陽狂 +被髮纓冠 +髮光可鑑 +髮際 +髮油 +髮網 +髮踊沖冠 +大金髮苔 +戴髮含齒 +斷髮文身 +吐哺捉髮 +吐哺握髮 +令人髮指 +含齒戴髮 +華髮 +黃髮 +心長髮短 +齒落髮白 +身體髮膚 +剷頭 +剷刈 +口燥唇乾 +舌乾唇焦 +花菴詞選 +渾箇 +箇中原因 +箇中理由 +箇中高手 +箇中好手 +箇中強手 +箇中滋味 +箇中奧秘 +箇中奧妙 +箇中玄機 +箇中消息 +箇中資訊 +箇中訊息 +對表達 +對表現 +對表演 +對表揚 +對表中 +對表明 +不準確 +一伙頭 +一伙食 +一半只 +一干弟兄 +一干弟子 +一干部下 +一斗斗 +一面食 +萬一只 +上面糊 +不克自制 +不准沒 +不加自制 +不占凶吉 +不占卜 +不占吉凶 +不占算 +不好干涉 +不好干預 +不干預 +不干涉 +不干休 +不干犯 +不干擾 +不干你 +不干我 +不干他 +不干她 +不干它 +不干事 +不斗膽 +不每只 +不采聲 +專向往 +丰容 +之一只 +之二只 +之八九只 +也斗了膽 +事情干脆 +事都干脆 +二只得 +亦云 +人云 +以自制 +們斗了膽 +你斗了膽 +其一只 +其二只 +其八九只 +內面包 +內面包的 +准保護 +准保釋 +几上 +几淨窗明 +几凳 +几子 +几旁 +几椅 +几榻 +几面上 +出征收 +擊扑 +划一槳 +划了一會 +划到岸 +划到江心 +前面店 +千只可 +千只夠 +千只怕 +千只能 +千只足夠 +半只可 +半只夠 +占了卜 +口干冒 +口干政 +口干涉 +口干犯 +口干預 +古書云 +古語云 +只占卜 +只占吉 +只占神問卜 +只占算 +只身上已 +只身上無 +只身上有 +只身上沒 +只身上的 +只身世 +只身為 +只身份 +只身體 +只身前 +只身受 +只身後 +只身子 +只身形 +只身影 +只身心 +只身旁 +只身材 +只身段 +只身邊 +只身首 +只身高 +只采聲 +可自制 +台子女 +台子孫 +台布景 +台面前 +合府上 +後面店 +向往常 +向往日 +向往時 +向往來 +唯一只 +喂了一聲 +喜向往 +四出徵收 +四面包 +多半只 +好斗大 +好斗室 +好斗笠 +好斗篷 +好斗膽 +好斗蓬 +家具體 +家具備 +家具有 +小几 +尸利 +尸祿 +尸臣 +尸鳩 +已占卜 +已占算 +并迭 +所云 +所云云 +所占卜 +所占星 +所占算 +手表決 +手表態 +手表明 +手表演 +手表現 +手表示 +手表達 +手表露 +手表面 +才干休 +才干戈 +才干擾 +才干政 +才干涉 +才干預 +扎好底子 +扎好根 +扑撻 +打吨 +折向往 +拉面上 +拉面具 +拉面前 +拉面巾 +拉面無 +拉面皮 +拉面罩 +拉面色 +拉面部 +捉奸黨 +捉奸徒 +捉奸細 +捉奸賊 +敢情欲 +敢斗了膽 +敲扑 +方向往 +望了望 +桌几 +每每只 +法自制 +洒滌 +洒淅 +洒濯 +洒然 +灘涂 +特制住 +特制定 +特制止 +特制訂 +百只可 +百只夠 +百只怕 +百只足夠 +皮制服 +相克制 +相克服 +短几 +石几 +秒表明 +秒表示 +窗明几亮 +竹几 +精制伏 +精制住 +精制服 +經有云 +給我干脆 +編制法 +能干休 +能干戈 +能干擾 +能干政 +能干涉 +能干預 +能自制 +自制一下 +自制下來 +自制不 +自制之力 +自制之能 +自制他 +自制伏 +自制你 +自制地 +自制她 +自制情 +自制我 +自制服 +自制的能 +自制能力 +船只得 +船只有 +船只能 +草荐 +荐居 +荐臻 +荐饑 +要自制 +語有云 +跌扑 +轉向往 +酒帘 +裡面包 +金表態 +金表情 +金表揚 +金表明 +金表演 +金表現 +金表示 +金表達 +金表露 +金表面 +長几 +隆准許 +雄斗斗 +面包住 +面包辦 +面包廂 +面包含 +面包圍 +面包容 +面包庇 +面包紮 +面包抄 +面包括 +面包攬 +面包涵 +面包管 +面包羅 +面包著 +面包藏 +面包裝 +面包裹 +面包起 +面店舖 +面粉碎 +面粉紅 +面食麵 +面食飯 +顛顛仆仆 +高干擾 +高干預 +高度自制 +黃金表 +天后宮 +一吊錢 +不食乾腊 +傳位于四太子 +儉确之教 +党懷英 +八蜡 +憑几 +南宮适 +大蜡 +子云 +小价 +歲聿云暮 +崖广 +恕乏价催 +悲筑 +折子戲 +揮杆 +搤肮拊背 +文采郁郁 +木杆 +洪适 +球杆 +腊之以為餌 +腊毒 +草广 +蜡月 +蜡祭 +言云 +貴价 +郁郁菲菲 +馬杆 +造麯 +麴生 +麴秀才 +麴塵 +麴櫱 +大麴 +黃麴毒素 +酒醴麴櫱 +麴道士 +麴錢 +麴車 +麴院 +鼠麴草 +不乾不淨 +生發生
\ No newline at end of file diff --git a/includes/zhtable/tradphrases_exclude.manual b/includes/zhtable/tradphrases_exclude.manual index 0db69513..9dadb6f7 100644 --- a/includes/zhtable/tradphrases_exclude.manual +++ b/includes/zhtable/tradphrases_exclude.manual @@ -12,6 +12,11 @@ 佈 纔 采 +着 +借 +甦 +荐 +担 可憐虫 一齣 上弔 @@ -161,3 +166,104 @@ 餘年 大阪 阪田 +豪杰 +七拚八湊 +一捲 +十捲 +上捲 +下捲 +加捲 +不捨 +不識檯舉 +稜登 +半弔子 +分布圖 +星鬥 +筋鬥 +斗鬨 +料鬥 +煙鬥 +熨鬥 +笆鬥 +箕鬥 +金鬥 +門鬥 +風鬥 +鬥子 +鬥笠 +老板娘 +剋制 +洋麵 +病癥 +製裁 +台製 +石家庄 +酒盃 +積极 +殭尸 +上梁不正 +項鍊 +鍊子 +鍊條 +拉鍊 +鉸鍊 +鍊鎖 +鐵鍊 +鍛鍊 +鍊乳 +鍊丹 +至于 +浮于 +附于 +次于 +于人 +助于 +行于 +于衷 +于事 +低于 +大于 +高于 +等于 +位于 +用于 +答覆 +複蓋 +反覆 +藉藉 +蘊藉 +蹈藉 +醞藉 +氆氌 +慰藉 +文藉 +枕藉 +狼藉 +別隻 +鼕鼕 +矇松雨 +佈雷 +丰度 +剪彩 +脣 +菴 +公裡 +箇中 +樑子 +樑書 +讚成 +讚同 +鐘表店 +精採 +鞭尸 +尸身 +尸首 +行尸走肉 +裹尸 +慼慼 +痠 +簑 +捱 +朝乾夕惕 +大曲酒 +神麴
\ No newline at end of file |