From 4f76b7a6d30cd77edec798f88be76cc6b9cbbaec Mon Sep 17 00:00:00 2001 From: GeorgeClark Date: Thu, 28 Jan 2010 19:10:56 +0000 Subject: [PATCH] Item8433: Restructured plugin, added feature to limit acronym links to first instance in a topic. git-svn-id: http://svn.foswiki.org/trunk/ControlWikiWordPlugin@6184 0b4bb1d4-4e5a-0410-9cc4-b2b747904278 --- data/System/ControlWikiWordPlugin.txt | 47 +++-- lib/Foswiki/Plugins/ControlWikiWordPlugin.pm | 144 ++++---------- .../Plugins/ControlWikiWordPlugin/Core.pm | 188 ++++++++++++++++++ .../Plugins/ControlWikiWordPlugin/MANIFEST | 2 +- .../ControlWikiWordPluginTests.pm | 36 +++- 5 files changed, 293 insertions(+), 124 deletions(-) create mode 100644 lib/Foswiki/Plugins/ControlWikiWordPlugin/Core.pm diff --git a/data/System/ControlWikiWordPlugin.txt b/data/System/ControlWikiWordPlugin.txt index dff514d..a669020 100644 --- a/data/System/ControlWikiWordPlugin.txt +++ b/data/System/ControlWikiWordPlugin.txt @@ -9,15 +9,18 @@ prefer. --> %SHORTDESCRIPTION% -%TOC% +%TOC{depth="3"}% ---++ Usage -This plugin provides 3 functions: - * Rules-based blocking of WikiWord linking backwards compatible to the !StopWikiWordPlugin (Configurable by any user) +This plugin provides 4 functions: + * Rules-based blocking of WikiWord linking backwards compatible to the !StopWikiWordPlugin + * Limit linking of Acronyms to the first instance in a topic. * !Singleton WikiWord linking backwards compatible to the !SingletonWikiWordPlugin * Regular Expression rules-based Singleton WikiWord linking (Configurable only by system administrator) +All functions are disabled by default unless explicitly enabled. + ----- ---+++ Rules-based blocking of WikiWord linking @@ -38,6 +41,33 @@ In this mode, the Plugin prevents automatic linking of selected [[%SYSTEMWEB%.Wi * LinuxWorld, MacDonald ----- +---+++ Acronym Linking Control + +In addition to WikiWords, Foswiki will auto-link Acronyms - any string +consisting of 3 or more upper-case letters and numbers. WikiWords are always +linked - displaying a question mark if the topic does not exist. Acronyms +however only link when the topic exists. + +In some technical +topics, this can result in excessive linking. This feature will limit +acronym links to only the first occurrence of an acronym in a topic. Forced +[[link]] however will always be honored. + +---++++ Configuration + +To limit Acronym linking, add the CONTROLWIKIWORDPLUGIN_LIMITACRONYMS setting +to the [[%LOCALSITEPREFS%]] topic or in any web in %WEBPREFSTOPIC%, or in user +or individual topics. This mode of operation is not enabled by default. (This +setting can be abbreviated as simply =LIMITACRONYMS=.) + + + * Set CONTROLWIKIWORDPLUGIN_LIMITACRONYMS = 1 + + +---++++ Examples +HTML is an example of an ACRONYM, as is RSS. If an ACRONYM occurs twice, only +the first instance will be linked. + ---+++ SingletonWikiWordPlugin compatible operation In this mode, the plugin will interpret that a single word is a reference to a Foswiki Topic of that name. To create a Singleton link, write a dot before the topic name. For example, if you have a topic named _Ontology_ you can link to it as _.Ontology_ This adds a simpler way to force a single word link. You can already accomplish the same thing by enclosing the word in double square brackets, like this: [[Ontology]] @@ -45,7 +75,6 @@ In this mode, the plugin will interpret that a single word is a reference to a F The syntax was chosen to be an extension of the _Web.Topic_ syntax. The syntax does not support topic names qualified with the web name. So to link to _Ontology_ in a different web than the current web, you need to use the square-bracket mode of forcing the links, [%NOP%[Support.Ontology][Support.Ontology]] ---++++ Configuration - To enable backwards compatible operation with the TWiki !SingletonWikiWordPlugin, Add the CONTROLWIKIWORDPLUGIN_DOTSINGLETONENABLE setting to the [[%LOCALSITEPREFS%]] topic or in any web in %WEBPREFSTOPIC%, or in user or individual topics. This mode of operation is not enabled by default. @@ -129,13 +158,3 @@ modifiers. The - "negates" or turns off the following options: | Support: | http://foswiki.org/bin/view/Support/ControlWikiWordPlugin | - - -%META:FORM{name="PackageForm"}% -%META:FIELD{name="ExtensionClassification" attributes="" %title="ExtensionClassification" value="Interface and Visualisation"}% -%META:FIELD{name="ExtensionType" attributes="" title="ExtensionType" value="PluginPackage"}% -%META:FIELD{name="Compatibility" attributes="" title="[[Compatibility]]" value="Should run with: All Foswiki versions."}% -%META:FIELD{name="DemoUrl" attributes="" title="DemoUrl" value=""}% -%META:FIELD{name="SupportUrl" attributes="" title="SupportUrl" value="Support.ControlWikiWordPlutin"}% -%META:FIELD{name="DevelopedInSVN" attributes="" title="DevelopedInSVN" value="Yes"}% -%META:FIELD{name="ModificationPolicy" attributes="" title="ModificationPolicy" value="PleaseFeelFreeToModify"}% diff --git a/lib/Foswiki/Plugins/ControlWikiWordPlugin.pm b/lib/Foswiki/Plugins/ControlWikiWordPlugin.pm index 60bc0bb..dfc8050 100644 --- a/lib/Foswiki/Plugins/ControlWikiWordPlugin.pm +++ b/lib/Foswiki/Plugins/ControlWikiWordPlugin.pm @@ -1,4 +1,3 @@ -# # Foswiki WikiClone ($wikiversion has version info) # # Copyright (C) 2010 George Clark, geonwiki@fenachrone.com @@ -21,8 +20,7 @@ # GNU General Public License for more details, published at # http://www.gnu.org/copyleft/gpl.html -# ========================= -package Foswiki::Plugins::ControlWikiWordPlugin; # change the package name!!! +package Foswiki::Plugins::ControlWikiWordPlugin; use strict; use warnings; @@ -55,135 +53,67 @@ our $SHORTDESCRIPTION = # entries so they can be used with =configure=. our $NO_PREFS_IN_TOPIC = 1; -# Module variables used between functions within this module -my $web; -my $topic; -my $user; -my $installWeb; -my $debug; - -my $stopWordsRE = ''; # Regex constructed from user input to stop linking -my $dotSINGLETON = 0; # Enable/Disable parameter for the .Singleton format -my $regexInput = ''; # Regex parameters passed from $Foswiki::cfg +my $disabled = 0; +my $web; # preRendering handler needs current web - passed from initPlugin +my %prefs; -# ========================= sub initPlugin { - ( $topic, $web, $user, $installWeb ) = @_; + + #my( $topic, $web, $user, $installWeb ) = @_; # check for Plugins.pm versions - if ( $Foswiki::Plugins::VERSION < 2 ) { - &Foswiki::Func::writeWarning( + if ( $Foswiki::Plugins::VERSION < 1 ) { + Foswiki::Func::writeWarning( "Version mismatch between ControlWikiWordPlugin and Plugins.pm"); return 0; } - # Get plugin debug flag - $debug = &Foswiki::Func::getPreferencesFlag("CONTROLWIKIWORDPLUGIN_DEBUG"); + if ( Foswiki::Func::getPreferencesFlag('NOAUTOLINK') ) + { # skip plugin if noautolink set for whole topic + $disabled = 1; + return 1; + } + + $web = $_[1]; + + $prefs{'regexInput'} = + $Foswiki::cfg{Plugins}{ControlWikiWordPlugin}{SingletonWords} + || {}; - my $stopWords = Foswiki::Func::getPreferencesValue("STOPWIKIWORDLINK") + $prefs{'stopWords'} = Foswiki::Func::getPreferencesValue("STOPWIKIWORDLINK") || Foswiki::Func::getPreferencesValue( "CONTROLWIKIWORDPLUGIN_STOPWIKIWORDLINK") || Foswiki::Func::getPreferencesValue( "STOPWIKIWORDLINKPLUGIN_STOPWIKIWORDLINK") || ''; - $stopWordsRE = ''; # Clear - handler only processes topic if provided - - if ($stopWords) { - - # build regularex: - $stopWords =~ s/\, */\|/go; - $stopWords =~ s/^ *//o; - $stopWords =~ s/ *$//o; - $stopWords =~ s/[^A-Za-z0-9\|]//go; - $stopWordsRE = "(^|[\( \n\r\t\|])($stopWords)" - ; # WikiWord preceeded by space or parens - Foswiki::Func::writeDebug( - "ControlWikiWordPlugin - stopWordsRE: $stopWordsRE") - if $debug; - } + $prefs{'controlAbbrev'} = + Foswiki::Func::getPreferencesValue("LIMITACRONYMS") + || Foswiki::Func::getPreferencesValue( + "CONTROLWIKIWORDPLUGIN_LIMITACRONYMS") + || ''; - $dotSINGLETON = Foswiki::Func::getPreferencesValue( + $prefs{'dotSINGLETON'} = Foswiki::Func::getPreferencesValue( "CONTROLWIKIWORDPLUGIN_DOTSINGLETONENABLE") || ''; - $regexInput = $Foswiki::cfg{Plugins}{ControlWikiWordPlugin}{SingletonWords} - || {}; + $disabled = 1 + unless ( $prefs{'regexInput'} + || $prefs{'stopWords'} + || $prefs{'controlAbbrev'} + || $prefs{'dotSINGLETON'} ); - # Plugin correctly initialized - Foswiki::Func::writeDebug( -"- Foswiki::Plugins::ControlWikiWordPlugin::initPlugin( $web.$topic ) is OK" - ) if $debug; return 1; } -#=========================================================================== sub preRenderingHandler { + ### my ( $text, $map ) = @_; + # + return if ($disabled); - # do not uncomment, use $_[0], $_[1]... instead - #my( $text, $pMap ) = @_; - - my $renderer = $Foswiki::Plugins::SESSION->renderer(); - my $removedTextareas = {}; - my $removedProtected = {}; - - $_[0] =~ s/$stopWordsRE/$1$2/g if ($stopWordsRE); - - # If we don't have any regex and don't want the dot format, forget it. - if ( scalar keys %$regexInput > 0 || $dotSINGLETON ) { - - # Don't bother at all if NOAUTOLINK is requested for the topic. - unless ( Foswiki::Func::getPreferencesFlag('NOAUTOLINK') ) { - -# SMELL: Directly calling Foswiki and Render functions is not recommended. -# This needs to be validated for any major changes in Foswiki. Tested on 1.0.9 and 1.1.0 trunk -# Determine which release of Foswiki in use - R1.1 moved takeOUtBlocks into Foswiki proper - - # Remove any blocks from the topic - eval( -'$renderer->takeOutBlocks( $_[0], \'noautolink\', $removedTextareas )' - ); - if ( $@ ne "" ) { - $_[0] = - Foswiki::takeOutBlocks( $_[0], 'noautolink', - $removedTextareas ); - } - - # Also remove any forced links from the topic. - $_[0] = - $renderer->_takeOutProtected( $_[0], qr/\[\[(?:.*?)\]\]/si, - 'wikilink', $removedProtected ); - $_[0] = $renderer->_takeOutProtected( - $_[0], qr//si, - 'htmllink', $removedProtected - ); - - foreach my $regex ( keys(%$regexInput) ) { - my $linkWeb = $regexInput->{$regex} || $web; - Foswiki::Func::writeDebug(" Regex is $regex Web is $linkWeb ") - if $debug; - $_[0] =~ s/(\s)($regex)\b/$1."[[$linkWeb.$2][$2]]"/ge; - } - $_[0] =~ s/(\s+)\.([A-Z]+[a-z]*)/"$1"."[[$web.$2][$2]]"/geo - if ($dotSINGLETON); - - # put back everything that was removed - if ($@) { - Foswiki::putBackBlocks( \$_[0], $removedTextareas, 'noautolink', - 'noautolink' ); - } - else { - $renderer->putBackBlocks( \$_[0], $removedTextareas, - 'noautolink', 'noautolink' ); - } - $renderer->_putBackProtected( \$_[0], 'wikilink', - $removedProtected ); - $renderer->_putBackProtected( \$_[0], 'htmllink', - $removedProtected ); - } - } - + require Foswiki::Plugins::ControlWikiWordPlugin::Core; + return Foswiki::Plugins::ControlWikiWordPlugin::Core::_preRender( $_[0], + $web, \%prefs ); } 1; - diff --git a/lib/Foswiki/Plugins/ControlWikiWordPlugin/Core.pm b/lib/Foswiki/Plugins/ControlWikiWordPlugin/Core.pm new file mode 100644 index 0000000..416008b --- /dev/null +++ b/lib/Foswiki/Plugins/ControlWikiWordPlugin/Core.pm @@ -0,0 +1,188 @@ +# +# Foswiki WikiClone ($wikiversion has version info) +# +# Copyright (C) 2010 George Clark, geonwiki@fenachrone.com +# +# This plugin contains code adapted from the SingletonWikiWordPlugin +# Copyright (C) 2000-2001 Andrea Sterbini, a.sterbini@flashnet.it +# Copyright (C) 2001 Peter Thoeny, Peter@Thoeny.com +# and the StopWikiWordLinkPlugin +# Copyright (C) 2006 Peter Thoeny, peter@thoeny.org +# All Rights Reserved. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details, published at +# http://www.gnu.org/copyleft/gpl.html + +package Foswiki::Plugins::ControlWikiWordPlugin::Core; + +use strict; + +BEGIN { + + # Do a dynamic 'use locale' for this module + if ( $Foswiki::useLocale || $Foswiki::cfg{UseLocale} ) { + require locale; + import locale(); + } +} + +my $initialised = 0; + +# Regular expressions used in module +my $webNameRegex; +my $abbrevRegex; + +#my $wikiWordRegex; +#my $upperAlphaRegex; +#my $lowerAlphaRegex; +#my $numericRegex; +#my $singleMixedAlphaNumRegex; + +# Module variables used between functions within this module +my $debug; +my $web; + +my %acronyms; + +#============= + +sub _lazyInit { + return 1 if $initialised; + $initialised = 1; + + $webNameRegex = Foswiki::Func::getRegularExpression('webNameRegex'); + $abbrevRegex = Foswiki::Func::getRegularExpression('abbrevRegex'); + + #$wikiWordRegex = Foswiki::Func::getRegularExpression('wikiWordRegex'); + #$upperAlphaRegex = Foswiki::Func::getRegularExpression('upperAlpha'); + #$lowerAlphaRegex = Foswiki::Func::getRegularExpression('lowerAlpha'); + #$numericRegex = Foswiki::Func::getRegularExpression('numeric'); + #$singleMixedAlphaNumRegex = + # qr/[$upperAlphaRegex$lowerAlphaRegex$numericRegex]/; + + #$regexInput = $Foswiki::cfg{Plugins}{ControlWikiWordPlugin}{SingletonWords} + # || {}; + + # Plugin correctly initialized + return 1; +} + +sub _preRender { + return unless _lazyInit(); + + $web = $_[1]; + + my $stopWords = $_[2]->{'stopWords'}; + my $regexInput = $_[2]->{'regexInput'}; + my $controlAbbrev = $_[2]->{'controlAbbrev'}; + my $dotSINGLETON = $_[2]->{'dotSINGLETON'}; + + my $stopWordsRE = ''; # Clear - handler only processes topic if provided + + if ($stopWords) { + + # build regularex: + $stopWords =~ s/\, */\|/go; + $stopWords =~ s/^ *//o; + $stopWords =~ s/ *$//o; + $stopWords =~ s/[^A-Za-z0-9\|]//go; + $stopWordsRE = "(^|[\( \n\r\t\|])($stopWords)" + ; # WikiWord preceeded by space or parens + Foswiki::Func::writeDebug( + "ControlWikiWordPlugin - stopWordsRE: $stopWordsRE") + if $debug; + } + + # Get plugin debug flag + $debug = &Foswiki::Func::getPreferencesFlag("CONTROLWIKIWORDPLUGIN_DEBUG"); + + my $renderer = $Foswiki::Plugins::SESSION->renderer(); + my $removedTextareas = {}; + my $removedProtected = {}; + + $_[0] =~ s/$stopWordsRE/$1$2/g if ($stopWordsRE); + + # If we don't have any regex and don't want the dot format, forget it. + if ( scalar keys %$regexInput > 0 || $dotSINGLETON || $controlAbbrev ) { + +# SMELL: Directly calling Foswiki and Render functions is not recommended. +# This needs to be validated for any major changes in Foswiki. Tested on 1.0.9 and 1.1.0 trunk +# Determine which release of Foswiki in use - R1.1 moved takeOUtBlocks into Foswiki proper + + # Remove any blocks from the topic + eval( +'$renderer->takeOutBlocks( $_[0], \'noautolink\', $removedTextareas )' + ); + if ( $@ ne "" ) { + $_[0] = + Foswiki::takeOutBlocks( $_[0], 'noautolink', $removedTextareas ); + } + + # Also remove any forced links from the topic. + $_[0] = + $renderer->_takeOutProtected( $_[0], qr/\[\[(?:.*?)\]\]/si, + 'wikilink', $removedProtected ); + $_[0] = + $renderer->_takeOutProtected( $_[0], qr//si, + 'htmllink', $removedProtected ); + + foreach my $regex ( keys(%$regexInput) ) { + my $linkWeb = $regexInput->{$regex} || $_[1]; + Foswiki::Func::writeDebug(" Regex is $regex Web is $linkWeb ") + if $debug; + $_[0] =~ s/(\s)($regex)\b/$1."[[$linkWeb.$2][$2]]"/ge; + } + + $_[0] =~ s/(\s+)\.([A-Z]+[a-z]*)/"$1"."[[$_[1].$2][$2]]"/geo + if ($dotSINGLETON); + + if ($controlAbbrev) { + undef %acronyms; + $_[0] =~ s/( + (?:^|(?<=[\s\(,])) # Prefix + (?:$webNameRegex\.)? # Webname. optional + (?:$abbrevRegex) # Abbreviation + ) + /&_findAbbrev($_[1],$1) + /geox; + } + + # put back everything that was removed + if ($@) { + Foswiki::putBackBlocks( \$_[0], $removedTextareas, 'noautolink', + 'noautolink' ); + } + else { + $renderer->putBackBlocks( \$_[0], $removedTextareas, 'noautolink', + 'noautolink' ); + } + $renderer->_putBackProtected( \$_[0], 'wikilink', $removedProtected ); + $renderer->_putBackProtected( \$_[0], 'htmllink', $removedProtected ); + } +} + +sub _findAbbrev { + + Foswiki::Func::writeDebug("Found abbrev $_[1] "); + Foswiki::Func::writeDebug(" - Searching $web if exists "); + if ( ( exists $acronyms{ $_[1] } ) + && ( Foswiki::Func::topicExists( $web, $_[1] ) ) ) + { + return "" . $_[1]; + } + else { + $acronyms{ $_[1] } = 1; + return $_[1]; + } +} + +1; + diff --git a/lib/Foswiki/Plugins/ControlWikiWordPlugin/MANIFEST b/lib/Foswiki/Plugins/ControlWikiWordPlugin/MANIFEST index c7e88b5..276bc13 100644 --- a/lib/Foswiki/Plugins/ControlWikiWordPlugin/MANIFEST +++ b/lib/Foswiki/Plugins/ControlWikiWordPlugin/MANIFEST @@ -2,5 +2,5 @@ data/System/ControlWikiWordPlugin.txt 0644 Documentation lib/Foswiki/Plugins/ControlWikiWordPlugin.pm 0644 Perl module lib/Foswiki/Plugins/ControlWikiWordPlugin/Config.spec 0644 - +lib/Foswiki/Plugins/ControlWikiWordPlugin/Core.pm 0644 diff --git a/test/unit/ControlWikiWordPlugin/ControlWikiWordPluginTests.pm b/test/unit/ControlWikiWordPlugin/ControlWikiWordPluginTests.pm index c8e5386..cb8dab7 100644 --- a/test/unit/ControlWikiWordPlugin/ControlWikiWordPluginTests.pm +++ b/test/unit/ControlWikiWordPlugin/ControlWikiWordPluginTests.pm @@ -74,7 +74,8 @@ sub doTest { Foswiki::Plugins::ControlWikiWordPlugin::initPlugin( "TestTopic", $this->{test_web}, "MyUser", "System" ); - Foswiki::Plugins::ControlWikiWordPlugin::preRenderingHandler($source); + Foswiki::Plugins::ControlWikiWordPlugin::preRenderingHandler( $source, + $this->{test_web} ); #print " RENDERED = $source \n"; if ($assertFalse) { @@ -257,7 +258,7 @@ END_EXPECTED } # ######################################################## -# Verify that WikiWords are blocked +# Verify that rules based wikiwords are linked # ######################################################## sub test_RulesBasedWikiWords { @@ -273,10 +274,12 @@ sub test_RulesBasedWikiWords { $source = <{test_web}.Plugins][Plugins]] END_EXPECTED $this->doTest( $source, $expected, 0 ); @@ -291,6 +294,35 @@ END_SOURCE $this->doTest( $source, $expected, 0 ); } +# ######################################################## +# Verify that Acronym linking can be controlled +# ######################################################## + +sub test_AcronymLimits { + my $this = shift; + + Foswiki::Func::setPreferencesValue( 'CONTROLWIKIWORDPLUGIN_LIMITACRONYMS', + '1' ); + + setLocalSite(); + + # Create the topic named HTML to verify 2nd Acronym link. + Foswiki::Func::saveTopicText( $this->{test_web}, "HTML", <HTML and [[HTML]] Test +END_EXPECTED + + $this->doTest( $source, $expected, 0 ); + +} + # #################### # Utility Functions ## # ####################