http://www.mediawiki.org/wiki/Special:Code/MediaWiki/89285

Revision: 89285
Author:   foxtrott
Date:     2011-06-01 20:49:33 +0000 (Wed, 01 Jun 2011)
Log Message:
-----------
Overhaul of extension

Modified Paths:
--------------
    trunk/extensions/Lingo/Lingo.php

Added Paths:
-----------
    trunk/extensions/Lingo/LingoBackend.php
    trunk/extensions/Lingo/LingoBasicBackend.php
    trunk/extensions/Lingo/LingoElement.php
    trunk/extensions/Lingo/LingoMessageLog.php
    trunk/extensions/Lingo/LingoParser.php
    trunk/extensions/Lingo/LingoTree.php
    trunk/extensions/Lingo/skins/
    trunk/extensions/Lingo/skins/Lingo.css
    trunk/extensions/Lingo/skins/linkicon.png

Modified: trunk/extensions/Lingo/Lingo.php
===================================================================
--- trunk/extensions/Lingo/Lingo.php    2011-06-01 20:32:56 UTC (rev 89284)
+++ trunk/extensions/Lingo/Lingo.php    2011-06-01 20:49:33 UTC (rev 89285)
@@ -7,7 +7,8 @@
  * @defgroup Lingo
  * @author Barry Coughlan
  * @copyright 2010 Barry Coughlan
- * @version 0.1
+ * @author Stephan Gambke
+ * @version 0.2 alpha
  * @licence GNU General Public Licence 2.0 or later
  * @see http://www.mediawiki.org/wiki/Extension:Lingo Documentation
  */
@@ -15,145 +16,54 @@
        die( 'This file is part of a MediaWiki extension, it is not a valid 
entry point.' );
 }
 
-// Extension credits that will show up on Special:Version
-$wgExtensionCredits['parserhook'][] = array(
+define( 'LINGO_VERSION', '0.2 alpha' );
+
+$wgExtensionCredits[ 'parserhook' ][ ] = array(
        'path' => __FILE__,
        'name' => 'Lingo',
-       'version' => '0.1',
-       'author' => 'Barry Coughlan',
+       'author' => array( 'Barry Coughlan', 
'[http://www.mediawiki.org/wiki/User:F.trott Stephan Gambke]' ),
        'url' => 'http://www.mediawiki.org/wiki/Extension:Lingo',
        'description' => 'Provides hover-over tool tips on articles from words 
defined on the [[Terminology]] page',
+       'version' => LINGO_VERSION,
 );
 
-$wgHooks['OutputPageBeforeHTML'][] = 'lingoHook';
+// server-local path to this file
+$wgexLingoDir = dirname( __FILE__ );
+$wgexLingoBackend = 'LingoBasicBackend';
 
-function lingoHook( &$out, &$text ) {
-       global $wgOut, $wgScriptPath;
-       $out->includeJQuery();
-       $out->addHeadItem( 'tooltip.css', '<link rel="stylesheet" 
type="text/css" href="' . $wgScriptPath . '/extensions/Lingo/tooltip.css"/>' );
-       $out->addHeadItem( 'tooltip.js', '<script type="text/javascript" src="' 
. $wgScriptPath . '/extensions/Lingo/tooltip.min.js"></script>' );
-       return $out;
-}
+// register message file
+// $wgExtensionMessagesFiles['Lingo'] = $dir . '/Lingo.i18n.php';
+// $wgExtensionMessagesFiles['LingoAlias'] = $dir . '/Lingo.alias.php';
 
-function getLingoArray( &$content ) {
-       $term = array();
-       $c = explode( "\n", $content );
+// register class files with the Autoloader
+// $wgAutoloadClasses['LingoSettings'] = $dir . '/LingoSettings.php';
+$wgAutoloadClasses['LingoParser'] = $wgexLingoDir . '/LingoParser.php';
+$wgAutoloadClasses['LingoTree'] = $wgexLingoDir . '/LingoTree.php';
+$wgAutoloadClasses['LingoElement'] = $wgexLingoDir . '/LingoElement.php';
+$wgAutoloadClasses['LingoBackend'] = $wgexLingoDir . '/LingoBackend.php';
+$wgAutoloadClasses['LingoBasicBackend'] = $wgexLingoDir . 
'/LingoBasicBackend.php';
+$wgAutoloadClasses['LingoMessageLog'] = $wgexLingoDir . '/LingoMessageLog.php';
+// $wgAutoloadClasses['SpecialLingoBrowser'] = $dir . 
'/SpecialLingoBrowser.php';
 
-       foreach ( $c as $entry ) {
-               if ( empty( $entry ) || $entry[ 0 ] !== ';' ) {
-                       continue;
-               }
+$wgHooks['ParserAfterTidy'][] = 'LingoParser::parse';
 
-               $terms = explode( ':', $entry, 2 );
-               if ( count( $terms ) < 2 ) {
-                       continue; // Invalid syntax
-               }
-               // Add to array
-               $term[trim( substr( $terms[0], 1 ) )] = trim( $terms[1] );
-       }
-       return $term;
-}
+// register resource modules with the Resource Loader
+$wgResourceModules['ext.Lingo'] = array(
+       // JavaScript and CSS styles. To combine multiple file, just list them 
as an array.
+       // 'scripts' => 'js/ext.myExtension.js',
+       'styles' => 'css/Lingo.css',
 
-$wgHooks['ParserAfterTidy'][] = 'lingoParser';
+       // When your module is loaded, these messages will be available to 
mediaWiki.msg()
+       // 'messages' => array( 'myextension-hello-world', 
'myextension-goodbye-world' ),
 
-function lingoParser( &$parser, &$text ) {
-       global $wgRequest;
+       // If your scripts need code from other modules, list their identifiers 
as dependencies
+       // and ResourceLoader will make sure they're loaded before you.
+       // You don't need to manually list 'mediawiki' or 'jquery', which are 
always loaded.
+       // 'dependencies' => array( 'jquery.ui.datepicker' ),
 
-       $action = $wgRequest->getVal( 'action', 'view' );
-       if ( $action == 'edit' || $action == 'ajax' || isset( 
$_POST['wpPreview'] ) ) {
-               return false;
-       }
+       // ResourceLoader needs to know where your files are; specify your
+       // subdir relative to "extensions" or $wgExtensionAssetsPath
+       'localBasePath' => dirname( __FILE__ ),
+       'remoteExtPath' => 'Lingo'
+);
 
-       // Get Terminology page
-       $rev = Revision::newFromTitle( Title::makeTitle( null, 'Terminology' ) 
);
-       if ( !$rev ) {
-               return false;
-       }
-       $content = &$rev->getText();
-       if ( empty( $content ) ) {
-               return false;
-       }
-
-       // Get array of terms
-       $terms = getLingoArray( $content );
-       // Get the minimum length abbreviation so we don't bother checking 
against words shorter than that
-       $min = min( array_map( 'strlen', array_keys( $terms ) ) );
-
-       // Parse HTML from page
-       // @todo FIXME: this works in PHP 5.3.3. What about 5.1?
-       wfSuppressWarnings();
-       $doc = DOMDocument::loadHTML( '<html><meta http-equiv="content-type" 
content="charset=utf-8"/>' . $text . '</html>' );
-       wfRestoreWarnings();
-
-       // Find all text in HTML.
-       $xpath = new DOMXpath( $doc );
-       $elements = $xpath->query( "//*[text()!=' ']/text()" );
-
-       // Iterate all HTML text matches
-       $nb = $elements->length;
-       $changed = false;
-       for ( $pos = 0; $pos < $nb; $pos++ ) {
-               $el = &$elements->item( $pos );
-               if ( strlen( $el->nodeValue ) < $min ) {
-                       continue;
-               }
-
-               // Split node text into words, putting offset and text into 
$offsets[0] array
-               preg_match_all(
-                       '/[^\s\.,;:]+/',
-                       $el->nodeValue,
-                       $offsets,
-                       PREG_OFFSET_CAPTURE
-               );
-
-               // Search and replace words in reverse order (from end of 
string backwards),
-               // This way we don't mess up the offsets of the words as we 
iterate
-               $len = count( $offsets[0] );
-               for ( $i = $len - 1; $i >= 0; $i-- ) {
-                       $offset = $offsets[0][$i];
-                       // Check if word is an abbreviation from the 
terminologies
-                       // Word matches, replace with appropriate span tag
-                       if ( !is_numeric( $offset[0] ) && isset( 
$terms[$offset[0]] ) ) {
-                               $changed = true;
-
-                               $tip = htmlentities( $terms[$offset[0]], 
ENT_COMPAT, 'UTF-8' );
-
-                               $beforeMatchNode = $doc->createTextNode( 
substr( $el->nodeValue, 0, $offset[1] ) );
-                               $afterMatchNode = $doc->createTextNode(
-                                       substr(
-                                               $el->nodeValue,
-                                               $offset[1] + strlen( $offset[0] 
),
-                                               strlen( $el->nodeValue ) - 1
-                                       )
-                               );
-
-                               // Wrap abbreviation in <span> tags
-                               $span = $doc->createElement( 'span', $offset[0] 
);
-                               $span->setAttribute( 'class', 'tooltip_abbr' );
-
-                               // Wrap definition in <span> tags, hidden
-                               $spanTip = $doc->createElement( 'span', $tip );
-                               $spanTip->setAttribute( 'class', 'tooltip_hide' 
);
-
-                               $el->parentNode->insertBefore( 
$beforeMatchNode, $el );
-                               $el->parentNode->insertBefore( $span, $el );
-                               $span->appendChild( $spanTip );
-                               $el->parentNode->insertBefore( $afterMatchNode, 
$el );
-                               $el->parentNode->removeChild( $el );
-                               // Set new element to the text before the match 
for next iteration
-                               $el = $beforeMatchNode;
-                       }
-               }
-       }
-
-       if ( $changed ) {
-               $body = $xpath->query( '/html/body' );
-
-               $text = '';
-               foreach ( $body->item( 0 )->childNodes as $child ) {
-                       $text .= $doc->saveXML( $child );
-               }
-       }
-
-       return true;
-}

Added: trunk/extensions/Lingo/LingoBackend.php
===================================================================
--- trunk/extensions/Lingo/LingoBackend.php                             (rev 0)
+++ trunk/extensions/Lingo/LingoBackend.php     2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * File holding the LingoBackend class
+ *
+ * @author Stephan Gambke
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * The LingoBackend class.
+ *
+ * @ingroup Lingo
+ */
+abstract class LingoBackend {
+
+       protected $mMessageLog;
+
+       public function __construct( LingoMessageLog &$messages = null ) {
+
+               $this->mMessageLog = $messages;
+
+       }
+
+       /**
+        *
+        * @return Boolean true, if a next element is available
+        */
+       abstract public function next();
+
+}
+


Property changes on: trunk/extensions/Lingo/LingoBackend.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/LingoBasicBackend.php
===================================================================
--- trunk/extensions/Lingo/LingoBasicBackend.php                                
(rev 0)
+++ trunk/extensions/Lingo/LingoBasicBackend.php        2011-06-01 20:49:33 UTC 
(rev 89285)
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * File holding the LingoBackend class
+ *
+ * @author Stephan Gambke
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * The LingoBasicBackend class.
+ *
+ * @ingroup Lingo
+ */
+class LingoBasicBackend extends LingoBackend {
+
+       protected $mArticleLines = array();
+
+       public function __construct( LingoMessageLog &$messages ) {
+
+               parent::__construct( $messages );
+
+               // Get Terminology page
+               $rev = Revision::newFromTitle( Title::makeTitle( null, 
'Terminology' ) );
+
+               if ( !$rev ) {
+                       $messages->addWarning( '[[Terminology]] does not 
exist.' );
+                       return false;
+               }
+
+               $content = $rev->getText();
+
+               $term = array();
+               $this->mArticleLines = explode( "\n", $content );
+       }
+
+       /**
+        *
+        * @return Boolean true, if a next element is available
+        */
+       public function next() {
+
+               $ret = null;
+
+               // find next valid line (yes, the assignation is intended)
+               while ( ( $ret == null ) && ( $entry = each( 
$this->mArticleLines ) ) ) {
+
+                       if ( empty( $entry[1] ) || $entry[1][0] !== ';' ) {
+                               continue;
+                       }
+
+                       $terms = explode( ':', $entry[1], 2 );
+
+                       if ( count( $terms ) < 2 ) {
+                               continue; // Invalid syntax
+                       }
+
+                       $ret = array(
+                               LingoElement::ELEMENT_TERM => trim( substr( 
$terms[0], 1 ) ),
+                               LingoElement::ELEMENT_DEFINITION => trim( 
$terms[1] ),
+                               LingoElement::ELEMENT_LINK => null,
+                               LingoElement::ELEMENT_SOURCE => null
+                       );
+               }
+
+               return $ret;
+       }
+
+}
+


Property changes on: trunk/extensions/Lingo/LingoBasicBackend.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/LingoElement.php
===================================================================
--- trunk/extensions/Lingo/LingoElement.php                             (rev 0)
+++ trunk/extensions/Lingo/LingoElement.php     2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * File holding the LingoElement class.
+ *
+ * @author Stephan Gambke
+ *
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * This class represents a term-definition pair.
+ * One term may be related to several definitions.
+ *
+ * @ingroup Lingo
+ */
+class LingoElement {
+       const ELEMENT_TERM = 0;
+       const ELEMENT_DEFINITION = 1;
+       const ELEMENT_SOURCE = 2;
+       const ELEMENT_LINK = 3;
+
+       private $mFullDefinition = null;
+       private $mDefinitions = array();
+       private $mTerm = null;
+       static private $mLinkTemplate = null;
+
+       public function __construct( &$term, &$definition = null ) {
+
+               $this->mTerm = $term;
+
+               if ( $definition ) {
+                       $this->addDefinition( $definition );
+               }
+       }
+
+       public function addDefinition( &$definition ) {
+               $this->mDefinitions[] = $definition;
+       }
+
+       public function getFullDefinition( DOMDocument &$doc ) {
+               // only create if not yet created
+               if ( $this->mFullDefinition == null || 
$this->mFullDefinition->ownerDocument !== $doc ) {
+
+                       // Wrap term and definition in <span> tags
+                       $span = $doc->createElement( 'span' );
+                       $span->setAttribute( 'class', 'tooltip' );
+
+                       // Wrap term in <span> tag, hidden
+                       $spanTerm = $doc->createElement( 'span', $this->mTerm );
+                       $spanTerm->setAttribute( 'class', 'tooltip_abbr' );
+
+                       // Wrap definition in two <span> tags
+                       $spanDefinitionOuter = $doc->createElement( 'span' );
+                       $spanDefinitionOuter->setAttribute( 'class', 
'tooltip_tipwrapper' );
+
+                       $spanDefinitionInner = $doc->createElement( 'span' );
+                       $spanDefinitionInner->setAttribute( 'class', 
'tooltip_tip' );
+
+                       foreach ( $this->mDefinitions as $definition ) {
+                               $element = $doc->createElement( 'span', 
htmlentities( $definition[self::ELEMENT_DEFINITION], ENT_COMPAT, 'UTF-8' ) . ' 
' );
+                               if ( $definition[self::ELEMENT_LINK] ) {
+                                       $linkedTitle = Title::newFromText( 
$definition[self::ELEMENT_LINK] );
+                                       if ( $linkedTitle ) {
+                                               $link = $this->getLinkTemplate( 
$doc );
+                                               $link->setAttribute( 'href', 
$linkedTitle->getFullURL() );
+                                               $element->appendChild( $link );
+                                       }
+                               }
+                               $spanDefinitionInner->appendChild( $element );
+                       }
+
+                       // insert term and definition
+                       $span->appendChild( $spanTerm );
+                       $span->appendChild( $spanDefinitionOuter );
+                       $spanDefinitionOuter->appendChild( $spanDefinitionInner 
);
+
+                       $this->mFullDefinition = $span;
+
+               }
+
+               return $this->mFullDefinition->cloneNode( true );
+       }
+
+       public function getCurrentKey() {
+               return key( $this->mDefinitions );
+       }
+
+       public function getTerm( $key ) {
+               return $this->mDefinitions[$key][self::ELEMENT_TERM];
+       }
+
+       public function getSource( &$key ) {
+               return $this->mDefinitions[$key][self::ELEMENT_SOURCE];
+       }
+
+       public function getDefinition( &$key ) {
+               return $this->mDefinitions[$key][self::ELEMENT_DEFINITION];
+       }
+
+       public function getLink( &$key ) {
+               return $this->mDefinitions[$key][self::ELEMENT_LINK];
+       }
+
+       public function next() {
+               next( $this->mDefinitions );
+       }
+
+       private function getLinkTemplate( DOMDocument &$doc ) {
+               // create template if it does not yet exist
+               if ( !self::$mLinkTemplate || ( 
self::$mLinkTemplate->ownerDocument !== $doc ) ) {
+                       global $wgScriptPath;
+
+                       $linkimage = $doc->createElement( 'img' );
+                       $linkimage->setAttribute( 'src', $wgScriptPath . 
'/extensions/SemanticLingo/skins/linkicon.png' );
+
+                       self::$mLinkTemplate = $doc->createElement( 'a' );
+                       self::$mLinkTemplate->appendChild( $linkimage );
+               }
+
+               return self::$mLinkTemplate->cloneNode( true );
+       }
+
+}


Property changes on: trunk/extensions/Lingo/LingoElement.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/LingoMessageLog.php
===================================================================
--- trunk/extensions/Lingo/LingoMessageLog.php                          (rev 0)
+++ trunk/extensions/Lingo/LingoMessageLog.php  2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * File holding the LingoMessageLog class.
+ *
+ * @author Stephan Gambke
+ *
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * This class holds messages (errors, warnings, notices) for Lingo
+ *
+ * Contains a static function to initiate the parsing.
+ *
+ * @ingroup Lingo
+ */
+class LingoMessageLog {
+
+       private $mMessages = array();
+       private $parser = null;
+
+       const MESSAGE_ERROR = 1;
+       const MESSAGE_WARNING = 2;
+       const MESSAGE_NOTICE = 3;
+
+       function addMessage( $message, $severity = self::MESSAGE_NOTICE ) {
+               $this->mMessages[] = array( $message, $severity );
+       }
+
+       function addError( $message ) {
+               $this->mMessages[] = array( $message, self::MESSAGE_ERROR );
+       }
+
+       function addWarning( $message ) {
+               $this->mMessages[] = array( $message, self::MESSAGE_WARNING );
+       }
+
+       function addNotice( $message ) {
+               $this->mMessages[] = array( $message, self::MESSAGE_NOTICE );
+       }
+
+       function getMessagesFormatted( $severity = self::MESSAGE_WARNING, 
$header = null ) {
+               global $wgTitle, $wgUser;
+
+               $ret = '';
+
+               if ( $header == null ) {
+                       $header = wfMsg( 'semanticglossary-messageheader' );
+               }
+
+               foreach ( $this->mMessages as $message ) {
+                       if ( $message[1] <= $severity ) {
+                               $ret .= '* ' . $message[0] . "\n";
+                       }
+               }
+
+               if ( $ret != '' ) {
+                       if ( !$this->parser ) {
+                               $parser = new Parser();
+                       }
+
+                       $ret = Html::rawElement( 'div', array( 'class' => 
'messages' ),
+                                       Html::rawElement( 'div', array( 'class' 
=> 'heading' ), $header ) .
+                                       $parser->parse( $ret, $wgTitle, 
ParserOptions::newFromUser( $wgUser ) )->getText()
+                       );
+               }
+
+               return $ret;
+       }
+
+}
+


Property changes on: trunk/extensions/Lingo/LingoMessageLog.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/LingoParser.php
===================================================================
--- trunk/extensions/Lingo/LingoParser.php                              (rev 0)
+++ trunk/extensions/Lingo/LingoParser.php      2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,286 @@
+<?php
+
+/**
+ * File holding the LingoParser class.
+ *
+ * @author Stephan Gambke
+ *
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * This class parses the given text and enriches it with definitions for 
defined
+ * terms.
+ *
+ * Contains a static function to initiate the parsing.
+ *
+ * @ingroup Lingo
+ */
+class LingoParser {
+
+       private $mLingoArray = null;
+       private $mLingoTree = null;
+       private $mLingoBackend = null;
+       private static $parserSingleton = null;
+
+       public function __construct() {
+               global $wgexLingoBackend;
+               $this->mLingoBackend = new $wgexLingoBackend( new 
LingoMessageLog() );
+       }
+
+       /**
+        *
+        * @param $parser
+        * @param $text
+        * @return Boolean
+        */
+       static function parse( &$parser, &$text ) {
+               wfProfileIn( __METHOD__ );
+               if ( !self::$parserSingleton ) {
+                       self::$parserSingleton = new LingoParser();
+               }
+
+               self::$parserSingleton->realParse( $parser, $text );
+
+               wfProfileOut( __METHOD__ );
+
+               return true;
+       }
+
+       /**
+        * Returns the list of terms applicable in the current context
+        *
+        * @return Array an array mapping terms (keys) to descriptions (values)
+        */
+       function getLingoArray( LingoMessageLog &$messages = null ) {
+               wfProfileIn( __METHOD__ );
+
+               // build glossary array only once per request
+               if ( !$this->mLingoArray ) {
+                       $this->buildLingo( $messages );
+               }
+
+               wfProfileOut( __METHOD__ );
+
+               return $this->mLingoArray;
+       }
+
+       /**
+        * Returns the list of terms applicable in the current context
+        *
+        * @return LingoTree a LingoTree mapping terms (keys) to descriptions 
(values)
+        */
+       function getLingoTree( LingoMessageLog &$messages = null ) {
+               wfProfileIn( __METHOD__ );
+
+               // build glossary array only once per request
+               if ( !$this->mLingoTree ) {
+                       $this->buildLingo( $messages );
+               }
+
+               wfProfileOut( __METHOD__ );
+
+               return $this->mLingoTree;
+       }
+
+       protected function buildLingo( LingoMessageLog &$messages = null ) {
+               wfProfileIn( __METHOD__ );
+
+               $this->mLingoTree = new LingoTree();
+
+               $backend = &$this->mLingoBackend;
+
+               // assemble the result array
+               $this->mLingoArray = array();
+               while ( $elementData = $backend->next() ) {
+
+                       if ( array_key_exists( ( $term = 
$elementData[LingoElement::ELEMENT_TERM] ), $this->mLingoArray ) ) {
+                               $this->mLingoArray[$term]->addDefinition( 
$elementData );
+                       } else {
+                               $this->mLingoArray[$term] = new LingoElement( 
$term, $elementData );
+                       }
+
+                       $this->mLingoTree->addTerm( $term, $elementData );
+               }
+
+               wfProfileOut( __METHOD__ );
+       }
+
+       /**
+        * Parses the given text and enriches applicable terms
+        *
+        * This method currently only recognizes terms consisting of max one 
word
+        *
+        * @param $parser
+        * @param $text
+        * @return Boolean
+        */
+       protected function realParse( &$parser, &$text ) {
+               global $wgRequest;
+
+               wfProfileIn( __METHOD__ );
+
+               $action = $wgRequest->getVal( 'action', 'view' );
+
+               if ( $text == null ||
+                       $text == '' ||
+                       $action == 'edit' ||
+                       $action == 'ajax' ||
+                       isset( $_POST['wpPreview'] )
+               ) {
+
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               }
+
+               // Get array of terms
+               $glossary = $this->getLingoTree();
+
+               if ( $glossary == null ) {
+                       wfProfileOut( __METHOD__ );
+                       return true;
+               }
+
+               // Parse HTML from page
+               // @todo FIXME: this works in PHP 5.3.3. What about 5.1?
+               wfProfileIn( __METHOD__ . ' 1 loadHTML' );
+               wfSuppressWarnings();
+
+               $doc = DOMDocument::loadHTML(
+                               '<html><meta http-equiv="content-type" 
content="charset=utf-8"/>' . $text . '</html>'
+               );
+
+               wfRestoreWarnings();
+               wfProfileOut( __METHOD__ . ' 1 loadHTML' );
+
+               wfProfileIn( __METHOD__ . ' 2 xpath' );
+               // Find all text in HTML.
+               $xpath = new DOMXpath( $doc );
+               $elements = $xpath->query(
+                               
"//*[not(ancestor-or-self::*[@class='noglossary'] or 
ancestor-or-self::a)][text()!=' ']/text()"
+               );
+               wfProfileOut( __METHOD__ . ' 2 xpath' );
+
+               // Iterate all HTML text matches
+               $nb = $elements->length;
+               $changedDoc = false;
+
+               for ( $pos = 0; $pos < $nb; $pos++ ) {
+                       $el = $elements->item( $pos );
+
+                       if ( strlen( $el->nodeValue ) < 
$glossary->getMinTermLength() ) {
+                               continue;
+                       }
+
+                       wfProfileIn( __METHOD__ . ' 3 lexer' );
+                       $matches = array();
+                       preg_match_all(
+                               '/[[:alpha:]]+|[^[:alpha:]]/u',
+                               $el->nodeValue,
+                               $matches,
+                               PREG_OFFSET_CAPTURE | PREG_PATTERN_ORDER
+                       );
+                       wfProfileOut( __METHOD__ . ' 3 lexer' );
+
+                       if ( count( $matches ) == 0 || count( $matches[0] ) == 
0 ) {
+                               continue;
+                       }
+
+                       $lexemes = &$matches[0];
+                       $countLexemes = count( $lexemes );
+                       $parent = &$el->parentNode;
+                       $index = 0;
+                       $changedElem = false;
+
+                       while ( $index < $countLexemes ) {
+                               wfProfileIn( __METHOD__ . ' 4 findNextTerm' );
+                               list( $skipped, $used, $definition ) =
+                                       $glossary->findNextTerm( $lexemes, 
$index, $countLexemes );
+                               wfProfileOut( __METHOD__ . ' 4 findNextTerm' );
+
+                               wfProfileIn( __METHOD__ . ' 5 insert' );
+                               if ( $used > 0 ) { // found a term
+                                       if ( $skipped > 0 ) { // skipped some 
text, insert it as is
+                                               $parent->insertBefore(
+                                                       $doc->createTextNode(
+                                                               substr( 
$el->nodeValue,
+                                                                       
$currLexIndex = $lexemes[$index][1],
+                                                                       
$lexemes[$index + $skipped][1] - $currLexIndex )
+                                                       ),
+                                                       $el
+                                               );
+                                       }
+
+                                       $parent->insertBefore( 
$definition->getFullDefinition( $doc ), $el );
+
+                                       $changedElem = true;
+                               } else { // did not find term, just use the 
rest of the text
+                                       // If we found no term now and no term 
before, there was no
+                                       // term in the whole element. Might as 
well not change the
+                                       // element at all.
+                                       // Only change element if found term 
before
+                                       if ( $changedElem ) {
+                                               $parent->insertBefore(
+                                                       $doc->createTextNode(
+                                                               substr( 
$el->nodeValue, $lexemes[$index][1] )
+                                                       ),
+                                                       $el
+                                               );
+                                       } else {
+                                               wfProfileOut( __METHOD__ . ' 5 
insert' );
+                                               // In principle superfluous, 
the loop would run out
+                                               // anyway. Might save a bit of 
time.
+                                               break;
+                                       }
+                               }
+                               wfProfileOut( __METHOD__ . ' 5 insert' );
+
+                               $index += $used + $skipped;
+                       }
+
+                       if ( $changedElem ) {
+                               $parent->removeChild( $el );
+                               $changedDoc = true;
+                       }
+               }
+
+               if ( $changedDoc ) {
+                       $body = $xpath->query( '/html/body' );
+
+                       $text = '';
+                       foreach ( $body->item( 0 )->childNodes as $child ) {
+                               $text .= $doc->saveXML( $child );
+                       }
+
+                       $this->loadModules( $parser );
+               }
+
+               wfProfileOut( __METHOD__ );
+
+               return true;
+       }
+
+       protected function loadModules( &$parser ) {
+               global $wgOut, $wgScriptPath;
+
+               if ( defined( 'MW_SUPPORTS_RESOURCE_MODULES' ) ) {
+                       if ( !is_null( $parser ) ) {
+                               $parser->getOutput()->addModules( 'ext.Lingo' );
+                       } else {
+                               $wgOut->addModules( 'ext.Lingo' );
+                       }
+               } else {
+                       if ( !is_null( $parser ) && ( $wgOut->isArticle() ) ) {
+                               $parser->getOutput()->addHeadItem( '<link 
rel="stylesheet" href="' . $wgScriptPath . '/extensions/Lingo/skins/Lingo.css" 
/>', 'ext.Lingo.css' );
+                       } else {
+                               $wgOut->addHeadItem( 'ext.Lingo.css', '<link 
rel="stylesheet" href="' . $wgScriptPath . '/extensions/Lingo/skins/Lingo.css" 
/>' );
+                       }
+               }
+       }
+
+}
+


Property changes on: trunk/extensions/Lingo/LingoParser.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/LingoTree.php
===================================================================
--- trunk/extensions/Lingo/LingoTree.php                                (rev 0)
+++ trunk/extensions/Lingo/LingoTree.php        2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * File holding the LingoTree class
+ *
+ * @author Stephan Gambke
+ *
+ * @file
+ * @ingroup Lingo
+ */
+if ( !defined( 'LINGO_VERSION' ) ) {
+       die( 'This file is part of the Lingo extension, it is not a valid entry 
point.' );
+}
+
+/**
+ * The LingoTree class.
+ *
+ * Vocabulary:
+ * Term - The term as a normal string
+ * Definition - Its definition object
+ * Element - An element (leaf) in the glossary tree
+ * Path - The path in the tree to the leaf representing a term
+ *
+ * @ingroup Lingo
+ */
+class LingoTree {
+
+       private $mTree = array();
+       private $mDefinition = null;
+       private $mMinLength = -1;
+
+       /**
+        * Adds a string to the Lingo Tree
+        * @param String $term
+        */
+       function addTerm( &$term, $definition ) {
+               if ( !$term ) {
+                       return;
+               }
+
+               $matches;
+               preg_match_all( '/[[:alpha:]]+|[^[:alpha:]]/u', $term, $matches 
);
+
+               $this->addElement( $matches[0], $term, $definition );
+
+               if ( $this->mMinLength > -1 ) {
+                       $this->mMinLength = min( array( $this->mMinLength, 
strlen( $term ) ) );
+               } else {
+                       $this->mMinLength = strlen( $term );
+               }
+       }
+
+       /**
+        * Recursively adds an element to the Lingo Tree
+        *
+        * @param array $path
+        * @param <type> $index
+        */
+       protected function addElement( Array &$path, &$term, &$definition ) {
+               // end of path, store description; end of recursion
+               if ( $path == null ) {
+                       $this -> addDefinition( $term, $definition );
+               } else {
+                       $step = array_shift( $path );
+
+                       if ( !array_key_exists( $step, $this->mTree ) ) {
+                               $this->mTree[$step] = new LingoTree();
+                       }
+
+                       $this->mTree[$step]->addElement( $path, $term, 
$definition );
+               }
+       }
+
+       /**
+        * Adds a defintion to the treenodes list of definitions
+        * @param <type> $definition
+        */
+       protected function addDefinition( &$term, &$definition ) {
+               if ( $this->mDefinition ) {
+                       $this->mDefinition->addDefinition( $definition );
+               } else {
+                       $this->mDefinition = new LingoElement( $term, 
$definition );
+               }
+       }
+
+       function getMinTermLength() {
+               return $this->mMinLength;
+       }
+
+       function findNextTerm( &$lexemes, $index, $countLexemes ) {
+               wfProfileIn( __METHOD__ );
+
+               $start = $lastindex = $index;
+               $definition = null;
+
+               // skip until ther start of a term is found
+               while ( $index < $countLexemes && !$definition ) {
+                       $currLex = &$lexemes[$index][0];
+
+                       // Did we find the start of a term?
+                       if ( array_key_exists( $currLex, $this->mTree ) ) {
+                               list( $lastindex, $definition ) = 
$this->mTree[$currLex]->findNextTermNoSkip( $lexemes, $index, $countLexemes );
+                       }
+
+                       // this will increase the index even if we found 
something;
+                       // will be corrected after the loop
+                       $index++;
+               }
+
+               wfProfileOut( __METHOD__ );
+               if ( $definition ) {
+                       return array( $index - $start - 1, $lastindex - $index 
+ 2, $definition );
+               } else {
+                       return array( $index - $start, 0, null );
+               }
+       }
+
+       function findNextTermNoSkip( &$lexemes, $index, $countLexemes ) {
+               wfProfileIn( __METHOD__ );
+
+               if ( $index + 1 < $countLexemes && array_key_exists( $currLex = 
$lexemes[$index + 1][0], $this->mTree ) ) {
+                       $ret = $this->mTree[$currLex]->findNextTermNoSkip( 
$lexemes, $index + 1, $countLexemes );
+               } else {
+                       $ret = array( $index, &$this->mDefinition );
+               }
+               wfProfileOut( __METHOD__ );
+               return $ret;
+       }
+
+}


Property changes on: trunk/extensions/Lingo/LingoTree.php
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/skins/Lingo.css
===================================================================
--- trunk/extensions/Lingo/skins/Lingo.css                              (rev 0)
+++ trunk/extensions/Lingo/skins/Lingo.css      2011-06-01 20:49:33 UTC (rev 
89285)
@@ -0,0 +1,64 @@
+/* 
+* Stylesheet for the markup of glossary terms in wiki pages.
+*/
+
+.tooltip {
+       display: inline;
+       position: relative;
+       cursor: help;
+}
+
+.tooltip_abbr {
+    border-bottom: 1px dotted #bbf;
+}
+
+.tooltip_tipwrapper {
+    display: none;
+
+       position: absolute;
+       top: 0;
+       left: 0;
+
+       height: 3em;
+       width: 23em;
+
+       font-weight: normal;
+       font-size: medium;
+       line-height: 1.2em;
+
+       z-index: 2;
+}
+
+.tooltip_tip {
+       display: block;
+
+       position: absolute;
+       top: 1.5em;
+       left: 2em;
+
+       width: 20em;
+
+       padding: 0.5em;
+       margin: 0;
+
+       background-color: #F9F9F9;
+       border: 1px solid #aaa;
+
+       -moz-border-radius: 5px;
+       border-radius: 5px;
+
+       -webkit-box-shadow: 3px 3px 3px #888;
+       box-shadow: 3px 3px 3px #888;
+}
+
+.tooltip_tip span {
+       display: block;
+}
+
+.tooltip:hover .tooltip_abbr {
+    background-color:rgba(0,0,0,0.1);
+}
+
+.tooltip:hover .tooltip_tipwrapper {
+    display: block;
+}


Property changes on: trunk/extensions/Lingo/skins/Lingo.css
___________________________________________________________________
Added: svn:eol-style
   + native

Added: trunk/extensions/Lingo/skins/linkicon.png
===================================================================
(Binary files differ)


Property changes on: trunk/extensions/Lingo/skins/linkicon.png
___________________________________________________________________
Added: svn:mime-type
   + image/png


_______________________________________________
MediaWiki-CVS mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-cvs

Reply via email to