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