r89285 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89284‎ | r89285 | r89286 >
Date:20:49, 1 June 2011
Author:foxtrott
Status:deferred
Tags:
Comment:
Overhaul of extension
Modified paths:
  • /trunk/extensions/Lingo/Lingo.php (modified) (history)
  • /trunk/extensions/Lingo/LingoBackend.php (added) (history)
  • /trunk/extensions/Lingo/LingoBasicBackend.php (added) (history)
  • /trunk/extensions/Lingo/LingoElement.php (added) (history)
  • /trunk/extensions/Lingo/LingoMessageLog.php (added) (history)
  • /trunk/extensions/Lingo/LingoParser.php (added) (history)
  • /trunk/extensions/Lingo/LingoTree.php (added) (history)
  • /trunk/extensions/Lingo/skins (added) (history)
  • /trunk/extensions/Lingo/skins/Lingo.css (added) (history)
  • /trunk/extensions/Lingo/skins/linkicon.png (added) (history)

Diff [purge]

Index: trunk/extensions/Lingo/LingoMessageLog.php
@@ -0,0 +1,77 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoMessageLog class.
 6+ *
 7+ * @author Stephan Gambke
 8+ *
 9+ * @file
 10+ * @ingroup Lingo
 11+ */
 12+if ( !defined( 'LINGO_VERSION' ) ) {
 13+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 14+}
 15+
 16+/**
 17+ * This class holds messages (errors, warnings, notices) for Lingo
 18+ *
 19+ * Contains a static function to initiate the parsing.
 20+ *
 21+ * @ingroup Lingo
 22+ */
 23+class LingoMessageLog {
 24+
 25+ private $mMessages = array();
 26+ private $parser = null;
 27+
 28+ const MESSAGE_ERROR = 1;
 29+ const MESSAGE_WARNING = 2;
 30+ const MESSAGE_NOTICE = 3;
 31+
 32+ function addMessage( $message, $severity = self::MESSAGE_NOTICE ) {
 33+ $this->mMessages[] = array( $message, $severity );
 34+ }
 35+
 36+ function addError( $message ) {
 37+ $this->mMessages[] = array( $message, self::MESSAGE_ERROR );
 38+ }
 39+
 40+ function addWarning( $message ) {
 41+ $this->mMessages[] = array( $message, self::MESSAGE_WARNING );
 42+ }
 43+
 44+ function addNotice( $message ) {
 45+ $this->mMessages[] = array( $message, self::MESSAGE_NOTICE );
 46+ }
 47+
 48+ function getMessagesFormatted( $severity = self::MESSAGE_WARNING, $header = null ) {
 49+ global $wgTitle, $wgUser;
 50+
 51+ $ret = '';
 52+
 53+ if ( $header == null ) {
 54+ $header = wfMsg( 'semanticglossary-messageheader' );
 55+ }
 56+
 57+ foreach ( $this->mMessages as $message ) {
 58+ if ( $message[1] <= $severity ) {
 59+ $ret .= '* ' . $message[0] . "\n";
 60+ }
 61+ }
 62+
 63+ if ( $ret != '' ) {
 64+ if ( !$this->parser ) {
 65+ $parser = new Parser();
 66+ }
 67+
 68+ $ret = Html::rawElement( 'div', array( 'class' => 'messages' ),
 69+ Html::rawElement( 'div', array( 'class' => 'heading' ), $header ) .
 70+ $parser->parse( $ret, $wgTitle, ParserOptions::newFromUser( $wgUser ) )->getText()
 71+ );
 72+ }
 73+
 74+ return $ret;
 75+ }
 76+
 77+}
 78+
Property changes on: trunk/extensions/Lingo/LingoMessageLog.php
___________________________________________________________________
Added: svn:eol-style
179 + native
Index: trunk/extensions/Lingo/LingoBackend.php
@@ -0,0 +1,36 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoBackend class
 6+ *
 7+ * @author Stephan Gambke
 8+ * @file
 9+ * @ingroup Lingo
 10+ */
 11+if ( !defined( 'LINGO_VERSION' ) ) {
 12+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 13+}
 14+
 15+/**
 16+ * The LingoBackend class.
 17+ *
 18+ * @ingroup Lingo
 19+ */
 20+abstract class LingoBackend {
 21+
 22+ protected $mMessageLog;
 23+
 24+ public function __construct( LingoMessageLog &$messages = null ) {
 25+
 26+ $this->mMessageLog = $messages;
 27+
 28+ }
 29+
 30+ /**
 31+ *
 32+ * @return Boolean true, if a next element is available
 33+ */
 34+ abstract public function next();
 35+
 36+}
 37+
Property changes on: trunk/extensions/Lingo/LingoBackend.php
___________________________________________________________________
Added: svn:eol-style
138 + native
Index: trunk/extensions/Lingo/skins/Lingo.css
@@ -0,0 +1,64 @@
 2+/*
 3+* Stylesheet for the markup of glossary terms in wiki pages.
 4+*/
 5+
 6+.tooltip {
 7+ display: inline;
 8+ position: relative;
 9+ cursor: help;
 10+}
 11+
 12+.tooltip_abbr {
 13+ border-bottom: 1px dotted #bbf;
 14+}
 15+
 16+.tooltip_tipwrapper {
 17+ display: none;
 18+
 19+ position: absolute;
 20+ top: 0;
 21+ left: 0;
 22+
 23+ height: 3em;
 24+ width: 23em;
 25+
 26+ font-weight: normal;
 27+ font-size: medium;
 28+ line-height: 1.2em;
 29+
 30+ z-index: 2;
 31+}
 32+
 33+.tooltip_tip {
 34+ display: block;
 35+
 36+ position: absolute;
 37+ top: 1.5em;
 38+ left: 2em;
 39+
 40+ width: 20em;
 41+
 42+ padding: 0.5em;
 43+ margin: 0;
 44+
 45+ background-color: #F9F9F9;
 46+ border: 1px solid #aaa;
 47+
 48+ -moz-border-radius: 5px;
 49+ border-radius: 5px;
 50+
 51+ -webkit-box-shadow: 3px 3px 3px #888;
 52+ box-shadow: 3px 3px 3px #888;
 53+}
 54+
 55+.tooltip_tip span {
 56+ display: block;
 57+}
 58+
 59+.tooltip:hover .tooltip_abbr {
 60+ background-color:rgba(0,0,0,0.1);
 61+}
 62+
 63+.tooltip:hover .tooltip_tipwrapper {
 64+ display: block;
 65+}
Property changes on: trunk/extensions/Lingo/skins/Lingo.css
___________________________________________________________________
Added: svn:eol-style
166 + native
Index: trunk/extensions/Lingo/skins/linkicon.png
Cannot display: file marked as a binary type.
svn:mime-type = image/png
Property changes on: trunk/extensions/Lingo/skins/linkicon.png
___________________________________________________________________
Added: svn:mime-type
267 + image/png
Index: trunk/extensions/Lingo/LingoBasicBackend.php
@@ -0,0 +1,74 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoBackend class
 6+ *
 7+ * @author Stephan Gambke
 8+ * @file
 9+ * @ingroup Lingo
 10+ */
 11+if ( !defined( 'LINGO_VERSION' ) ) {
 12+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 13+}
 14+
 15+/**
 16+ * The LingoBasicBackend class.
 17+ *
 18+ * @ingroup Lingo
 19+ */
 20+class LingoBasicBackend extends LingoBackend {
 21+
 22+ protected $mArticleLines = array();
 23+
 24+ public function __construct( LingoMessageLog &$messages ) {
 25+
 26+ parent::__construct( $messages );
 27+
 28+ // Get Terminology page
 29+ $rev = Revision::newFromTitle( Title::makeTitle( null, 'Terminology' ) );
 30+
 31+ if ( !$rev ) {
 32+ $messages->addWarning( '[[Terminology]] does not exist.' );
 33+ return false;
 34+ }
 35+
 36+ $content = $rev->getText();
 37+
 38+ $term = array();
 39+ $this->mArticleLines = explode( "\n", $content );
 40+ }
 41+
 42+ /**
 43+ *
 44+ * @return Boolean true, if a next element is available
 45+ */
 46+ public function next() {
 47+
 48+ $ret = null;
 49+
 50+ // find next valid line (yes, the assignation is intended)
 51+ while ( ( $ret == null ) && ( $entry = each( $this->mArticleLines ) ) ) {
 52+
 53+ if ( empty( $entry[1] ) || $entry[1][0] !== ';' ) {
 54+ continue;
 55+ }
 56+
 57+ $terms = explode( ':', $entry[1], 2 );
 58+
 59+ if ( count( $terms ) < 2 ) {
 60+ continue; // Invalid syntax
 61+ }
 62+
 63+ $ret = array(
 64+ LingoElement::ELEMENT_TERM => trim( substr( $terms[0], 1 ) ),
 65+ LingoElement::ELEMENT_DEFINITION => trim( $terms[1] ),
 66+ LingoElement::ELEMENT_LINK => null,
 67+ LingoElement::ELEMENT_SOURCE => null
 68+ );
 69+ }
 70+
 71+ return $ret;
 72+ }
 73+
 74+}
 75+
Property changes on: trunk/extensions/Lingo/LingoBasicBackend.php
___________________________________________________________________
Added: svn:eol-style
176 + native
Index: trunk/extensions/Lingo/LingoElement.php
@@ -0,0 +1,128 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoElement class.
 6+ *
 7+ * @author Stephan Gambke
 8+ *
 9+ * @file
 10+ * @ingroup Lingo
 11+ */
 12+if ( !defined( 'LINGO_VERSION' ) ) {
 13+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 14+}
 15+
 16+/**
 17+ * This class represents a term-definition pair.
 18+ * One term may be related to several definitions.
 19+ *
 20+ * @ingroup Lingo
 21+ */
 22+class LingoElement {
 23+ const ELEMENT_TERM = 0;
 24+ const ELEMENT_DEFINITION = 1;
 25+ const ELEMENT_SOURCE = 2;
 26+ const ELEMENT_LINK = 3;
 27+
 28+ private $mFullDefinition = null;
 29+ private $mDefinitions = array();
 30+ private $mTerm = null;
 31+ static private $mLinkTemplate = null;
 32+
 33+ public function __construct( &$term, &$definition = null ) {
 34+
 35+ $this->mTerm = $term;
 36+
 37+ if ( $definition ) {
 38+ $this->addDefinition( $definition );
 39+ }
 40+ }
 41+
 42+ public function addDefinition( &$definition ) {
 43+ $this->mDefinitions[] = $definition;
 44+ }
 45+
 46+ public function getFullDefinition( DOMDocument &$doc ) {
 47+ // only create if not yet created
 48+ if ( $this->mFullDefinition == null || $this->mFullDefinition->ownerDocument !== $doc ) {
 49+
 50+ // Wrap term and definition in <span> tags
 51+ $span = $doc->createElement( 'span' );
 52+ $span->setAttribute( 'class', 'tooltip' );
 53+
 54+ // Wrap term in <span> tag, hidden
 55+ $spanTerm = $doc->createElement( 'span', $this->mTerm );
 56+ $spanTerm->setAttribute( 'class', 'tooltip_abbr' );
 57+
 58+ // Wrap definition in two <span> tags
 59+ $spanDefinitionOuter = $doc->createElement( 'span' );
 60+ $spanDefinitionOuter->setAttribute( 'class', 'tooltip_tipwrapper' );
 61+
 62+ $spanDefinitionInner = $doc->createElement( 'span' );
 63+ $spanDefinitionInner->setAttribute( 'class', 'tooltip_tip' );
 64+
 65+ foreach ( $this->mDefinitions as $definition ) {
 66+ $element = $doc->createElement( 'span', htmlentities( $definition[self::ELEMENT_DEFINITION], ENT_COMPAT, 'UTF-8' ) . ' ' );
 67+ if ( $definition[self::ELEMENT_LINK] ) {
 68+ $linkedTitle = Title::newFromText( $definition[self::ELEMENT_LINK] );
 69+ if ( $linkedTitle ) {
 70+ $link = $this->getLinkTemplate( $doc );
 71+ $link->setAttribute( 'href', $linkedTitle->getFullURL() );
 72+ $element->appendChild( $link );
 73+ }
 74+ }
 75+ $spanDefinitionInner->appendChild( $element );
 76+ }
 77+
 78+ // insert term and definition
 79+ $span->appendChild( $spanTerm );
 80+ $span->appendChild( $spanDefinitionOuter );
 81+ $spanDefinitionOuter->appendChild( $spanDefinitionInner );
 82+
 83+ $this->mFullDefinition = $span;
 84+
 85+ }
 86+
 87+ return $this->mFullDefinition->cloneNode( true );
 88+ }
 89+
 90+ public function getCurrentKey() {
 91+ return key( $this->mDefinitions );
 92+ }
 93+
 94+ public function getTerm( $key ) {
 95+ return $this->mDefinitions[$key][self::ELEMENT_TERM];
 96+ }
 97+
 98+ public function getSource( &$key ) {
 99+ return $this->mDefinitions[$key][self::ELEMENT_SOURCE];
 100+ }
 101+
 102+ public function getDefinition( &$key ) {
 103+ return $this->mDefinitions[$key][self::ELEMENT_DEFINITION];
 104+ }
 105+
 106+ public function getLink( &$key ) {
 107+ return $this->mDefinitions[$key][self::ELEMENT_LINK];
 108+ }
 109+
 110+ public function next() {
 111+ next( $this->mDefinitions );
 112+ }
 113+
 114+ private function getLinkTemplate( DOMDocument &$doc ) {
 115+ // create template if it does not yet exist
 116+ if ( !self::$mLinkTemplate || ( self::$mLinkTemplate->ownerDocument !== $doc ) ) {
 117+ global $wgScriptPath;
 118+
 119+ $linkimage = $doc->createElement( 'img' );
 120+ $linkimage->setAttribute( 'src', $wgScriptPath . '/extensions/SemanticLingo/skins/linkicon.png' );
 121+
 122+ self::$mLinkTemplate = $doc->createElement( 'a' );
 123+ self::$mLinkTemplate->appendChild( $linkimage );
 124+ }
 125+
 126+ return self::$mLinkTemplate->cloneNode( true );
 127+ }
 128+
 129+}
Property changes on: trunk/extensions/Lingo/LingoElement.php
___________________________________________________________________
Added: svn:eol-style
1130 + native
Index: trunk/extensions/Lingo/LingoParser.php
@@ -0,0 +1,286 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoParser class.
 6+ *
 7+ * @author Stephan Gambke
 8+ *
 9+ * @file
 10+ * @ingroup Lingo
 11+ */
 12+if ( !defined( 'LINGO_VERSION' ) ) {
 13+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 14+}
 15+
 16+/**
 17+ * This class parses the given text and enriches it with definitions for defined
 18+ * terms.
 19+ *
 20+ * Contains a static function to initiate the parsing.
 21+ *
 22+ * @ingroup Lingo
 23+ */
 24+class LingoParser {
 25+
 26+ private $mLingoArray = null;
 27+ private $mLingoTree = null;
 28+ private $mLingoBackend = null;
 29+ private static $parserSingleton = null;
 30+
 31+ public function __construct() {
 32+ global $wgexLingoBackend;
 33+ $this->mLingoBackend = new $wgexLingoBackend( new LingoMessageLog() );
 34+ }
 35+
 36+ /**
 37+ *
 38+ * @param $parser
 39+ * @param $text
 40+ * @return Boolean
 41+ */
 42+ static function parse( &$parser, &$text ) {
 43+ wfProfileIn( __METHOD__ );
 44+ if ( !self::$parserSingleton ) {
 45+ self::$parserSingleton = new LingoParser();
 46+ }
 47+
 48+ self::$parserSingleton->realParse( $parser, $text );
 49+
 50+ wfProfileOut( __METHOD__ );
 51+
 52+ return true;
 53+ }
 54+
 55+ /**
 56+ * Returns the list of terms applicable in the current context
 57+ *
 58+ * @return Array an array mapping terms (keys) to descriptions (values)
 59+ */
 60+ function getLingoArray( LingoMessageLog &$messages = null ) {
 61+ wfProfileIn( __METHOD__ );
 62+
 63+ // build glossary array only once per request
 64+ if ( !$this->mLingoArray ) {
 65+ $this->buildLingo( $messages );
 66+ }
 67+
 68+ wfProfileOut( __METHOD__ );
 69+
 70+ return $this->mLingoArray;
 71+ }
 72+
 73+ /**
 74+ * Returns the list of terms applicable in the current context
 75+ *
 76+ * @return LingoTree a LingoTree mapping terms (keys) to descriptions (values)
 77+ */
 78+ function getLingoTree( LingoMessageLog &$messages = null ) {
 79+ wfProfileIn( __METHOD__ );
 80+
 81+ // build glossary array only once per request
 82+ if ( !$this->mLingoTree ) {
 83+ $this->buildLingo( $messages );
 84+ }
 85+
 86+ wfProfileOut( __METHOD__ );
 87+
 88+ return $this->mLingoTree;
 89+ }
 90+
 91+ protected function buildLingo( LingoMessageLog &$messages = null ) {
 92+ wfProfileIn( __METHOD__ );
 93+
 94+ $this->mLingoTree = new LingoTree();
 95+
 96+ $backend = &$this->mLingoBackend;
 97+
 98+ // assemble the result array
 99+ $this->mLingoArray = array();
 100+ while ( $elementData = $backend->next() ) {
 101+
 102+ if ( array_key_exists( ( $term = $elementData[LingoElement::ELEMENT_TERM] ), $this->mLingoArray ) ) {
 103+ $this->mLingoArray[$term]->addDefinition( $elementData );
 104+ } else {
 105+ $this->mLingoArray[$term] = new LingoElement( $term, $elementData );
 106+ }
 107+
 108+ $this->mLingoTree->addTerm( $term, $elementData );
 109+ }
 110+
 111+ wfProfileOut( __METHOD__ );
 112+ }
 113+
 114+ /**
 115+ * Parses the given text and enriches applicable terms
 116+ *
 117+ * This method currently only recognizes terms consisting of max one word
 118+ *
 119+ * @param $parser
 120+ * @param $text
 121+ * @return Boolean
 122+ */
 123+ protected function realParse( &$parser, &$text ) {
 124+ global $wgRequest;
 125+
 126+ wfProfileIn( __METHOD__ );
 127+
 128+ $action = $wgRequest->getVal( 'action', 'view' );
 129+
 130+ if ( $text == null ||
 131+ $text == '' ||
 132+ $action == 'edit' ||
 133+ $action == 'ajax' ||
 134+ isset( $_POST['wpPreview'] )
 135+ ) {
 136+
 137+ wfProfileOut( __METHOD__ );
 138+ return true;
 139+ }
 140+
 141+ // Get array of terms
 142+ $glossary = $this->getLingoTree();
 143+
 144+ if ( $glossary == null ) {
 145+ wfProfileOut( __METHOD__ );
 146+ return true;
 147+ }
 148+
 149+ // Parse HTML from page
 150+ // @todo FIXME: this works in PHP 5.3.3. What about 5.1?
 151+ wfProfileIn( __METHOD__ . ' 1 loadHTML' );
 152+ wfSuppressWarnings();
 153+
 154+ $doc = DOMDocument::loadHTML(
 155+ '<html><meta http-equiv="content-type" content="charset=utf-8"/>' . $text . '</html>'
 156+ );
 157+
 158+ wfRestoreWarnings();
 159+ wfProfileOut( __METHOD__ . ' 1 loadHTML' );
 160+
 161+ wfProfileIn( __METHOD__ . ' 2 xpath' );
 162+ // Find all text in HTML.
 163+ $xpath = new DOMXpath( $doc );
 164+ $elements = $xpath->query(
 165+ "//*[not(ancestor-or-self::*[@class='noglossary'] or ancestor-or-self::a)][text()!=' ']/text()"
 166+ );
 167+ wfProfileOut( __METHOD__ . ' 2 xpath' );
 168+
 169+ // Iterate all HTML text matches
 170+ $nb = $elements->length;
 171+ $changedDoc = false;
 172+
 173+ for ( $pos = 0; $pos < $nb; $pos++ ) {
 174+ $el = $elements->item( $pos );
 175+
 176+ if ( strlen( $el->nodeValue ) < $glossary->getMinTermLength() ) {
 177+ continue;
 178+ }
 179+
 180+ wfProfileIn( __METHOD__ . ' 3 lexer' );
 181+ $matches = array();
 182+ preg_match_all(
 183+ '/[[:alpha:]]+|[^[:alpha:]]/u',
 184+ $el->nodeValue,
 185+ $matches,
 186+ PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER
 187+ );
 188+ wfProfileOut( __METHOD__ . ' 3 lexer' );
 189+
 190+ if ( count( $matches ) == 0 || count( $matches[0] ) == 0 ) {
 191+ continue;
 192+ }
 193+
 194+ $lexemes = &$matches[0];
 195+ $countLexemes = count( $lexemes );
 196+ $parent = &$el->parentNode;
 197+ $index = 0;
 198+ $changedElem = false;
 199+
 200+ while ( $index < $countLexemes ) {
 201+ wfProfileIn( __METHOD__ . ' 4 findNextTerm' );
 202+ list( $skipped, $used, $definition ) =
 203+ $glossary->findNextTerm( $lexemes, $index, $countLexemes );
 204+ wfProfileOut( __METHOD__ . ' 4 findNextTerm' );
 205+
 206+ wfProfileIn( __METHOD__ . ' 5 insert' );
 207+ if ( $used > 0 ) { // found a term
 208+ if ( $skipped > 0 ) { // skipped some text, insert it as is
 209+ $parent->insertBefore(
 210+ $doc->createTextNode(
 211+ substr( $el->nodeValue,
 212+ $currLexIndex = $lexemes[$index][1],
 213+ $lexemes[$index + $skipped][1] - $currLexIndex )
 214+ ),
 215+ $el
 216+ );
 217+ }
 218+
 219+ $parent->insertBefore( $definition->getFullDefinition( $doc ), $el );
 220+
 221+ $changedElem = true;
 222+ } else { // did not find term, just use the rest of the text
 223+ // If we found no term now and no term before, there was no
 224+ // term in the whole element. Might as well not change the
 225+ // element at all.
 226+ // Only change element if found term before
 227+ if ( $changedElem ) {
 228+ $parent->insertBefore(
 229+ $doc->createTextNode(
 230+ substr( $el->nodeValue, $lexemes[$index][1] )
 231+ ),
 232+ $el
 233+ );
 234+ } else {
 235+ wfProfileOut( __METHOD__ . ' 5 insert' );
 236+ // In principle superfluous, the loop would run out
 237+ // anyway. Might save a bit of time.
 238+ break;
 239+ }
 240+ }
 241+ wfProfileOut( __METHOD__ . ' 5 insert' );
 242+
 243+ $index += $used + $skipped;
 244+ }
 245+
 246+ if ( $changedElem ) {
 247+ $parent->removeChild( $el );
 248+ $changedDoc = true;
 249+ }
 250+ }
 251+
 252+ if ( $changedDoc ) {
 253+ $body = $xpath->query( '/html/body' );
 254+
 255+ $text = '';
 256+ foreach ( $body->item( 0 )->childNodes as $child ) {
 257+ $text .= $doc->saveXML( $child );
 258+ }
 259+
 260+ $this->loadModules( $parser );
 261+ }
 262+
 263+ wfProfileOut( __METHOD__ );
 264+
 265+ return true;
 266+ }
 267+
 268+ protected function loadModules( &$parser ) {
 269+ global $wgOut, $wgScriptPath;
 270+
 271+ if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
 272+ if ( !is_null( $parser ) ) {
 273+ $parser->getOutput()->addModules( 'ext.Lingo' );
 274+ } else {
 275+ $wgOut->addModules( 'ext.Lingo' );
 276+ }
 277+ } else {
 278+ if ( !is_null( $parser ) && ( $wgOut->isArticle() ) ) {
 279+ $parser->getOutput()->addHeadItem( '<link rel="stylesheet" href="' . $wgScriptPath . '/extensions/Lingo/skins/Lingo.css" />', 'ext.Lingo.css' );
 280+ } else {
 281+ $wgOut->addHeadItem( 'ext.Lingo.css', '<link rel="stylesheet" href="' . $wgScriptPath . '/extensions/Lingo/skins/Lingo.css" />' );
 282+ }
 283+ }
 284+ }
 285+
 286+}
 287+
Property changes on: trunk/extensions/Lingo/LingoParser.php
___________________________________________________________________
Added: svn:eol-style
1288 + native
Index: trunk/extensions/Lingo/Lingo.php
@@ -7,7 +7,8 @@
88 * @defgroup Lingo
99 * @author Barry Coughlan
1010 * @copyright 2010 Barry Coughlan
11 - * @version 0.1
 11+ * @author Stephan Gambke
 12+ * @version 0.2 alpha
1213 * @licence GNU General Public Licence 2.0 or later
1314 * @see http://www.mediawiki.org/wiki/Extension:Lingo Documentation
1415 */
@@ -15,145 +16,54 @@
1617 die( 'This file is part of a MediaWiki extension, it is not a valid entry point.' );
1718 }
1819
19 -// Extension credits that will show up on Special:Version
20 -$wgExtensionCredits['parserhook'][] = array(
 20+define( 'LINGO_VERSION', '0.2 alpha' );
 21+
 22+$wgExtensionCredits[ 'parserhook' ][ ] = array(
2123 'path' => __FILE__,
2224 'name' => 'Lingo',
23 - 'version' => '0.1',
24 - 'author' => 'Barry Coughlan',
 25+ 'author' => array( 'Barry Coughlan', '[http://www.mediawiki.org/wiki/User:F.trott Stephan Gambke]' ),
2526 'url' => 'http://www.mediawiki.org/wiki/Extension:Lingo',
2627 'description' => 'Provides hover-over tool tips on articles from words defined on the [[Terminology]] page',
 28+ 'version' => LINGO_VERSION,
2729 );
2830
29 -$wgHooks['OutputPageBeforeHTML'][] = 'lingoHook';
 31+// server-local path to this file
 32+$wgexLingoDir = dirname( __FILE__ );
 33+$wgexLingoBackend = 'LingoBasicBackend';
3034
31 -function lingoHook( &$out, &$text ) {
32 - global $wgOut, $wgScriptPath;
33 - $out->includeJQuery();
34 - $out->addHeadItem( 'tooltip.css', '<link rel="stylesheet" type="text/css" href="' . $wgScriptPath . '/extensions/Lingo/tooltip.css"/>' );
35 - $out->addHeadItem( 'tooltip.js', '<script type="text/javascript" src="' . $wgScriptPath . '/extensions/Lingo/tooltip.min.js"></script>' );
36 - return $out;
37 -}
 35+// register message file
 36+// $wgExtensionMessagesFiles['Lingo'] = $dir . '/Lingo.i18n.php';
 37+// $wgExtensionMessagesFiles['LingoAlias'] = $dir . '/Lingo.alias.php';
3838
39 -function getLingoArray( &$content ) {
40 - $term = array();
41 - $c = explode( "\n", $content );
 39+// register class files with the Autoloader
 40+// $wgAutoloadClasses['LingoSettings'] = $dir . '/LingoSettings.php';
 41+$wgAutoloadClasses['LingoParser'] = $wgexLingoDir . '/LingoParser.php';
 42+$wgAutoloadClasses['LingoTree'] = $wgexLingoDir . '/LingoTree.php';
 43+$wgAutoloadClasses['LingoElement'] = $wgexLingoDir . '/LingoElement.php';
 44+$wgAutoloadClasses['LingoBackend'] = $wgexLingoDir . '/LingoBackend.php';
 45+$wgAutoloadClasses['LingoBasicBackend'] = $wgexLingoDir . '/LingoBasicBackend.php';
 46+$wgAutoloadClasses['LingoMessageLog'] = $wgexLingoDir . '/LingoMessageLog.php';
 47+// $wgAutoloadClasses['SpecialLingoBrowser'] = $dir . '/SpecialLingoBrowser.php';
4248
43 - foreach ( $c as $entry ) {
44 - if ( empty( $entry ) || $entry[ 0 ] !== ';' ) {
45 - continue;
46 - }
 49+$wgHooks['ParserAfterTidy'][] = 'LingoParser::parse';
4750
48 - $terms = explode( ':', $entry, 2 );
49 - if ( count( $terms ) < 2 ) {
50 - continue; // Invalid syntax
51 - }
52 - // Add to array
53 - $term[trim( substr( $terms[0], 1 ) )] = trim( $terms[1] );
54 - }
55 - return $term;
56 -}
 51+// register resource modules with the Resource Loader
 52+$wgResourceModules['ext.Lingo'] = array(
 53+ // JavaScript and CSS styles. To combine multiple file, just list them as an array.
 54+ // 'scripts' => 'js/ext.myExtension.js',
 55+ 'styles' => 'css/Lingo.css',
5756
58 -$wgHooks['ParserAfterTidy'][] = 'lingoParser';
 57+ // When your module is loaded, these messages will be available to mediaWiki.msg()
 58+ // 'messages' => array( 'myextension-hello-world', 'myextension-goodbye-world' ),
5959
60 -function lingoParser( &$parser, &$text ) {
61 - global $wgRequest;
 60+ // If your scripts need code from other modules, list their identifiers as dependencies
 61+ // and ResourceLoader will make sure they're loaded before you.
 62+ // You don't need to manually list 'mediawiki' or 'jquery', which are always loaded.
 63+ // 'dependencies' => array( 'jquery.ui.datepicker' ),
6264
63 - $action = $wgRequest->getVal( 'action', 'view' );
64 - if ( $action == 'edit' || $action == 'ajax' || isset( $_POST['wpPreview'] ) ) {
65 - return false;
66 - }
 65+ // ResourceLoader needs to know where your files are; specify your
 66+ // subdir relative to "extensions" or $wgExtensionAssetsPath
 67+ 'localBasePath' => dirname( __FILE__ ),
 68+ 'remoteExtPath' => 'Lingo'
 69+);
6770
68 - // Get Terminology page
69 - $rev = Revision::newFromTitle( Title::makeTitle( null, 'Terminology' ) );
70 - if ( !$rev ) {
71 - return false;
72 - }
73 - $content = &$rev->getText();
74 - if ( empty( $content ) ) {
75 - return false;
76 - }
77 -
78 - // Get array of terms
79 - $terms = getLingoArray( $content );
80 - // Get the minimum length abbreviation so we don't bother checking against words shorter than that
81 - $min = min( array_map( 'strlen', array_keys( $terms ) ) );
82 -
83 - // Parse HTML from page
84 - // @todo FIXME: this works in PHP 5.3.3. What about 5.1?
85 - wfSuppressWarnings();
86 - $doc = DOMDocument::loadHTML( '<html><meta http-equiv="content-type" content="charset=utf-8"/>' . $text . '</html>' );
87 - wfRestoreWarnings();
88 -
89 - // Find all text in HTML.
90 - $xpath = new DOMXpath( $doc );
91 - $elements = $xpath->query( "//*[text()!=' ']/text()" );
92 -
93 - // Iterate all HTML text matches
94 - $nb = $elements->length;
95 - $changed = false;
96 - for ( $pos = 0; $pos < $nb; $pos++ ) {
97 - $el = &$elements->item( $pos );
98 - if ( strlen( $el->nodeValue ) < $min ) {
99 - continue;
100 - }
101 -
102 - // Split node text into words, putting offset and text into $offsets[0] array
103 - preg_match_all(
104 - '/[^\s\.,;:]+/',
105 - $el->nodeValue,
106 - $offsets,
107 - PREG_OFFSET_CAPTURE
108 - );
109 -
110 - // Search and replace words in reverse order (from end of string backwards),
111 - // This way we don't mess up the offsets of the words as we iterate
112 - $len = count( $offsets[0] );
113 - for ( $i = $len - 1; $i >= 0; $i-- ) {
114 - $offset = $offsets[0][$i];
115 - // Check if word is an abbreviation from the terminologies
116 - // Word matches, replace with appropriate span tag
117 - if ( !is_numeric( $offset[0] ) && isset( $terms[$offset[0]] ) ) {
118 - $changed = true;
119 -
120 - $tip = htmlentities( $terms[$offset[0]], ENT_COMPAT, 'UTF-8' );
121 -
122 - $beforeMatchNode = $doc->createTextNode( substr( $el->nodeValue, 0, $offset[1] ) );
123 - $afterMatchNode = $doc->createTextNode(
124 - substr(
125 - $el->nodeValue,
126 - $offset[1] + strlen( $offset[0] ),
127 - strlen( $el->nodeValue ) - 1
128 - )
129 - );
130 -
131 - // Wrap abbreviation in <span> tags
132 - $span = $doc->createElement( 'span', $offset[0] );
133 - $span->setAttribute( 'class', 'tooltip_abbr' );
134 -
135 - // Wrap definition in <span> tags, hidden
136 - $spanTip = $doc->createElement( 'span', $tip );
137 - $spanTip->setAttribute( 'class', 'tooltip_hide' );
138 -
139 - $el->parentNode->insertBefore( $beforeMatchNode, $el );
140 - $el->parentNode->insertBefore( $span, $el );
141 - $span->appendChild( $spanTip );
142 - $el->parentNode->insertBefore( $afterMatchNode, $el );
143 - $el->parentNode->removeChild( $el );
144 - // Set new element to the text before the match for next iteration
145 - $el = $beforeMatchNode;
146 - }
147 - }
148 - }
149 -
150 - if ( $changed ) {
151 - $body = $xpath->query( '/html/body' );
152 -
153 - $text = '';
154 - foreach ( $body->item( 0 )->childNodes as $child ) {
155 - $text .= $doc->saveXML( $child );
156 - }
157 - }
158 -
159 - return true;
160 -}
Index: trunk/extensions/Lingo/LingoTree.php
@@ -0,0 +1,130 @@
 2+<?php
 3+
 4+/**
 5+ * File holding the LingoTree class
 6+ *
 7+ * @author Stephan Gambke
 8+ *
 9+ * @file
 10+ * @ingroup Lingo
 11+ */
 12+if ( !defined( 'LINGO_VERSION' ) ) {
 13+ die( 'This file is part of the Lingo extension, it is not a valid entry point.' );
 14+}
 15+
 16+/**
 17+ * The LingoTree class.
 18+ *
 19+ * Vocabulary:
 20+ * Term - The term as a normal string
 21+ * Definition - Its definition object
 22+ * Element - An element (leaf) in the glossary tree
 23+ * Path - The path in the tree to the leaf representing a term
 24+ *
 25+ * @ingroup Lingo
 26+ */
 27+class LingoTree {
 28+
 29+ private $mTree = array();
 30+ private $mDefinition = null;
 31+ private $mMinLength = -1;
 32+
 33+ /**
 34+ * Adds a string to the Lingo Tree
 35+ * @param String $term
 36+ */
 37+ function addTerm( &$term, $definition ) {
 38+ if ( !$term ) {
 39+ return;
 40+ }
 41+
 42+ $matches;
 43+ preg_match_all( '/[[:alpha:]]+|[^[:alpha:]]/u', $term, $matches );
 44+
 45+ $this->addElement( $matches[0], $term, $definition );
 46+
 47+ if ( $this->mMinLength > -1 ) {
 48+ $this->mMinLength = min( array( $this->mMinLength, strlen( $term ) ) );
 49+ } else {
 50+ $this->mMinLength = strlen( $term );
 51+ }
 52+ }
 53+
 54+ /**
 55+ * Recursively adds an element to the Lingo Tree
 56+ *
 57+ * @param array $path
 58+ * @param <type> $index
 59+ */
 60+ protected function addElement( Array &$path, &$term, &$definition ) {
 61+ // end of path, store description; end of recursion
 62+ if ( $path == null ) {
 63+ $this -> addDefinition( $term, $definition );
 64+ } else {
 65+ $step = array_shift( $path );
 66+
 67+ if ( !array_key_exists( $step, $this->mTree ) ) {
 68+ $this->mTree[$step] = new LingoTree();
 69+ }
 70+
 71+ $this->mTree[$step]->addElement( $path, $term, $definition );
 72+ }
 73+ }
 74+
 75+ /**
 76+ * Adds a defintion to the treenodes list of definitions
 77+ * @param <type> $definition
 78+ */
 79+ protected function addDefinition( &$term, &$definition ) {
 80+ if ( $this->mDefinition ) {
 81+ $this->mDefinition->addDefinition( $definition );
 82+ } else {
 83+ $this->mDefinition = new LingoElement( $term, $definition );
 84+ }
 85+ }
 86+
 87+ function getMinTermLength() {
 88+ return $this->mMinLength;
 89+ }
 90+
 91+ function findNextTerm( &$lexemes, $index, $countLexemes ) {
 92+ wfProfileIn( __METHOD__ );
 93+
 94+ $start = $lastindex = $index;
 95+ $definition = null;
 96+
 97+ // skip until ther start of a term is found
 98+ while ( $index < $countLexemes && !$definition ) {
 99+ $currLex = &$lexemes[$index][0];
 100+
 101+ // Did we find the start of a term?
 102+ if ( array_key_exists( $currLex, $this->mTree ) ) {
 103+ list( $lastindex, $definition ) = $this->mTree[$currLex]->findNextTermNoSkip( $lexemes, $index, $countLexemes );
 104+ }
 105+
 106+ // this will increase the index even if we found something;
 107+ // will be corrected after the loop
 108+ $index++;
 109+ }
 110+
 111+ wfProfileOut( __METHOD__ );
 112+ if ( $definition ) {
 113+ return array( $index - $start - 1, $lastindex - $index + 2, $definition );
 114+ } else {
 115+ return array( $index - $start, 0, null );
 116+ }
 117+ }
 118+
 119+ function findNextTermNoSkip( &$lexemes, $index, $countLexemes ) {
 120+ wfProfileIn( __METHOD__ );
 121+
 122+ if ( $index + 1 < $countLexemes && array_key_exists( $currLex = $lexemes[$index + 1][0], $this->mTree ) ) {
 123+ $ret = $this->mTree[$currLex]->findNextTermNoSkip( $lexemes, $index + 1, $countLexemes );
 124+ } else {
 125+ $ret = array( $index, &$this->mDefinition );
 126+ }
 127+ wfProfileOut( __METHOD__ );
 128+ return $ret;
 129+ }
 130+
 131+}
Property changes on: trunk/extensions/Lingo/LingoTree.php
___________________________________________________________________
Added: svn:eol-style
1132 + native

Status & tagging log