/* This file is part of leafdigital leafChat. leafChat 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 3 of the License, or (at your option) any later version. leafChat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with leafChat. If not, see <http://www.gnu.org/licenses/>. Copyright 2012 Samuel Marshall. */ package com.leafdigital.highlighter; import java.util.*; import java.util.regex.Pattern; import org.w3c.dom.*; import util.xml.*; import com.leafdigital.audio.api.*; import com.leafdigital.highlighter.api.Highlighter; import com.leafdigital.prefs.api.*; import com.leafdigital.prefsui.api.PreferencesUI; import leafchat.core.api.*; /** * Plugin for handling text highlighting. */ public class HighlighterPlugin implements Plugin, Highlighter { /** * Preference for words within highlighter anon children. */ public final static String PREF_WORD = "word"; /** * Preference for whether nickname is highlighted. */ public final static String PREF_HIGHLIGHT_NICKNAME = "highlight-nickname"; /** * Default nickname highlight (on). */ public final static String PREFDEFAULT_HIGHLIGHT_NICKNAME="t"; /** * Preference for highlight sound ("" = none) */ public final static String PREF_HIGHLIGHT_SOUND = "highlight-sound"; /** * Default value for highlight sound ("") */ public final static String PREFDEFAULT_HIGHLIGHT_SOUND = ""; /** * Preference for whether highlight sound should be restricted (not played * more than once per minute). */ public final static String PREF_HIGHLIGHT_RESTRICTSOUND = "highlight-restrict-sound"; /** * Default restrict value (on) */ public final static String PREFDEFAULT_HIGHLIGHT_RESTRICTSOUND = "t"; private final static long RESTRICT_TIME = 60000L; private final static Set<String> STOP_ELEMENTS = new HashSet<String>(Arrays.asList( new String[] { "nick", "server", "chan", "owntext"})); private PluginContext context; private long lastSound; @Override public synchronized void init( PluginContext context, PluginLoadReporter reporter) throws GeneralException { this.context=context; // Become a singleton context.registerSingleton(Highlighter.class, this); // Register prefs page PreferencesUI preferencesUI = context.getSingle(PreferencesUI.class); preferencesUI.registerPage(this,(new HighlighterPage(context)).getPage()); } @Override public String toString() { // Used to display in system log etc. return "Highlighter plugin"; } @Override public String highlight(String currentNickname, String xml) throws XMLException { // Get data from prefs Preferences prefs = context.getSingle(Preferences.class); PreferencesGroup group = prefs.getGroup(this); PreferencesGroup[] anon = group.getAnon(); boolean includeNickname = currentNickname != null && prefs.toBoolean(group.get(PREF_HIGHLIGHT_NICKNAME, PREFDEFAULT_HIGHLIGHT_NICKNAME)); // Build list of highlight words in regular expression format StringBuffer words = new StringBuffer(); for(int i=0; i<anon.length; i++) { if(i != 0) { words.append('|'); } words.append(Pattern.quote(anon[i].get(PREF_WORD))); } if(includeNickname) { if(anon.length > 0) { words.append('|'); } words.append(Pattern.quote(currentNickname)); } // Don't do anything if there are no words to find if(words.length() == 0) { return xml; } // Parse document and apply highlight Document d = XML.parse("<o>" + xml.replaceAll("xx", "xx ") + "</o>"); String patternString = "(^|\\W)(" + words + ")(\\W|$)"; Pattern pattern = Pattern.compile(patternString, Pattern.CASE_INSENSITIVE); boolean changed = highlight(d.getDocumentElement(), pattern); if(!changed) { return xml; } // Play sound if it's enabled String sound = group.get(PREF_HIGHLIGHT_SOUND, PREFDEFAULT_HIGHLIGHT_SOUND); if(!sound.equals("")) { boolean restrict = prefs.toBoolean(group.get(PREF_HIGHLIGHT_RESTRICTSOUND, PREFDEFAULT_HIGHLIGHT_RESTRICTSOUND)); long now = System.currentTimeMillis(); if(!restrict || (now - lastSound > RESTRICT_TIME)) { Audio audio = context.getSingle(Audio.class); if(audio.soundExists(sound)) { lastSound = now; try { audio.play(sound); } catch(AudioSetupException e) { // Ignore errors if the user's computer can't play sounds } catch(GeneralException e) { ErrorMsg.report("Error playing highlight sound", e); } } else { // If the sound doesn't exist any more, turn off the pref group.unset(PREF_HIGHLIGHT_SOUND); } } } // Save result string and chop outer tag String result = XML.saveString(d); result = result.replaceAll("xxHIGHLIGHT", "<highlight>").replaceAll( "xx/HIGHLIGHT", "</highlight>").replaceAll("xx ", "xx"); return result.substring(3, result.length()-4); } private boolean highlight(Element parent, Pattern pattern) { boolean result = false; NodeList nl = parent.getChildNodes(); for(int i=0; i<nl.getLength(); i++) { Node n = nl.item(i); if(n instanceof Element) { Element e = (Element)n; if(STOP_ELEMENTS.contains(e.getTagName())) { continue; } result = result || highlight(e, pattern); } if(n instanceof Text) { Text t = (Text)n; String text = t.getData(); String before = text; text = pattern.matcher(text).replaceAll("$1xxHIGHLIGHT$2xx/HIGHLIGHT$3"); if(!before.equals(text)) { result = true; t.setData(text); } } } return result; } @Override public void close() throws GeneralException { } }