001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. The ASF licenses this file to You
004: * under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License. For additional information regarding
015: * copyright in this work, please see the NOTICE file in the top level
016: * directory of this distribution.
017: */
018:
019: package org.apache.roller.ui.rendering.plugins;
020:
021: import java.util.Collection;
022: import java.util.Iterator;
023: import java.util.Map;
024: import java.util.regex.Matcher;
025: import java.util.regex.Pattern;
026: import java.util.regex.PatternSyntaxException;
027: import org.apache.commons.lang.StringEscapeUtils;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.apache.roller.RollerException;
031: import org.apache.roller.business.BookmarkManager;
032: import org.apache.roller.business.RollerFactory;
033: import org.apache.roller.business.WeblogEntryPlugin;
034: import org.apache.roller.pojos.BookmarkData;
035: import org.apache.roller.pojos.FolderData;
036: import org.apache.roller.pojos.WeblogEntryData;
037: import org.apache.roller.pojos.WebsiteData;
038:
039: /**
040: * Automatically insert links into entry text based on users bookmarks.
041: */
042: public class BookmarkPlugin implements WeblogEntryPlugin {
043:
044: private static Log mLogger = LogFactory
045: .getLog(BookmarkPlugin.class);
046:
047: protected String name = "Bookmark Linker";
048: protected String description = "Automatically uses your Bookmarks to "
049: + "create links. Simply use the Name of a Bookmark and it will be "
050: + "converted into a hyperlink using the Bookmark's URL.";
051:
052: public BookmarkPlugin() {
053: mLogger.debug("BookmarkPlugin instantiated.");
054: }
055:
056: public String getName() {
057: return name;
058: }
059:
060: public String getDescription() {
061: return StringEscapeUtils.escapeJavaScript(description);
062: }
063:
064: public void init(WebsiteData website) throws RollerException {
065: }
066:
067: public String render(WeblogEntryData entry, String str) {
068: String text = str;
069: try {
070: BookmarkManager bMgr = RollerFactory.getRoller()
071: .getBookmarkManager();
072: FolderData rootFolder = bMgr.getRootFolder(entry
073: .getWebsite());
074: text = matchBookmarks(text, rootFolder);
075: text = lookInFolders(text, rootFolder.getFolders());
076: } catch (RollerException e) {
077: // nothing much I can do, go with default "Weblog" value
078: // could be RollerException or NullPointerException
079: mLogger.warn(e);
080: }
081: return text;
082: }
083:
084: /**
085: * Recursively travel down Folder tree, attempting
086: * to match up Bookmarks in each Folder.
087: *
088: * @param text
089: * @param folders
090: * @return
091: */
092: private String lookInFolders(String text, Collection folders) {
093: Iterator it = folders.iterator();
094: while (it.hasNext()) {
095: FolderData folder = (FolderData) it.next();
096: text = matchBookmarks(text, folder);
097:
098: try {
099: if (!folder.getFolders().isEmpty()) {
100: lookInFolders(text, folder.getFolders());
101: }
102: } catch (RollerException e) {
103: mLogger.error("Error getting child Folders");
104: }
105: }
106: return text;
107: }
108:
109: private String matchBookmarks(String text, FolderData folder) {
110: Iterator bookmarks = folder.getBookmarks().iterator();
111: String workingText = text;
112: while (bookmarks.hasNext()) {
113: BookmarkData bookmark = (BookmarkData) bookmarks.next();
114: String bkDescription = bookmark.getDescription();
115: if (bkDescription == null)
116: bkDescription = "";
117: String bookmarkLink = "<a href=\"" + bookmark.getUrl()
118: + "\" title=\"" + bkDescription + "\">"
119: + bookmark.getName() + "</a>";
120: try {
121: // Replace all occurrences of bookmark name that don't occur within the bounds of an anchor tag
122: // Notes:
123: // - use reluctant quantifiers on the tags to avoid gobbling more than desired
124: // - use non-capturing groups for boundaries to avoid replacing the boundary as well as the bookmark name.
125: // - we depend on the numbering of the specific groups in this expression in the replacement code below.
126: // TODO: should escape the bookmark name
127: String regEx = "(<a(?:\\s.*?)??/>)|(<a(?:\\s.*?)??>)|(</a(?:\\s.*?)??>)|(?:\\b)("
128: + bookmark.getName() + ")(?:\\b)";
129: Matcher m = Pattern.compile(regEx).matcher(workingText);
130: StringBuffer textBuf = new StringBuffer(workingText
131: .length());
132: int inLink = 0;
133: while (m.find()) {
134: if (m.group(1) != null) {
135: // self-closed anchor tag <a ... /> -- ignore
136: } else if (m.group(2) != null) {
137: // matched opening anchor tag <a ...>
138: inLink++;
139: } else if (m.group(3) != null) {
140: // closing anchor tag </a>, but ignore nonmatching ones
141: if (inLink > 0)
142: inLink--;
143: } else if (m.group(4) != null) {
144: // matched the bookmark -- replace, but only if not within a link tag.
145: if (inLink == 0)
146: m.appendReplacement(textBuf, bookmarkLink);
147: }
148: // Any remaining case indicates a bug. One could add an else with assertion here. Conservatively don't substitute.
149: }
150: m.appendTail(textBuf);
151: workingText = textBuf.toString();
152: } catch (PatternSyntaxException e) {
153: // Can happen since we don't escape pattern the bookmark name to protect pattern characters.
154: mLogger.warn("Failed to substitute for bookmark ["
155: + bookmark.getName()
156: + "] due to regular expression characters.");
157: }
158: }
159: return workingText.toString();
160: }
161:
162: }
|