diff --git a/grails-app/services/org/jggug/wiki/CacheService.groovy b/grails-app/services/org/jggug/wiki/CacheService.groovy new file mode 100644 index 0000000..a0a3daa --- /dev/null +++ b/grails-app/services/org/jggug/wiki/CacheService.groovy @@ -0,0 +1,40 @@ +package org.jggug.wiki + +import net.sf.ehcache.Ehcache +import net.sf.ehcache.Element + +class CacheService { + + static transactional = false + + Ehcache contentCache + Ehcache wikiCache + + def getContent(key) { + contentCache.get(key)?.getValue() + } + + def putContent(key,value) { + def old = getContent(key) + contentCache.put(new Element(key,value)) + return old + } + + def removeContent(key) { + contentCache.remove key + } + + def getWikiText(key) { + wikiCache.get(key)?.getValue() + } + + def removeWikiText(key) { + wikiCache.remove key + } + + def putWikiText(key,value) { + def old = getWikiText(key) + wikiCache.put(new Element(key,value)) + return old + } +} \ No newline at end of file diff --git a/src/groovy/org/jggug/wiki/GrailsWikiEngine.groovy b/src/groovy/org/jggug/wiki/GrailsWikiEngine.groovy new file mode 100644 index 0000000..1ff36b1 --- /dev/null +++ b/src/groovy/org/jggug/wiki/GrailsWikiEngine.groovy @@ -0,0 +1,446 @@ +package org.jggug.wiki + +import org.codehaus.groovy.control.CompilerConfiguration + +import org.radeox.engine.BaseRenderEngine +import org.radeox.api.engine.WikiRenderEngine + +import org.radeox.macro.BaseMacro +import org.radeox.macro.parameter.MacroParameter +import org.radeox.filter.regex.RegexTokenFilter + +import org.radeox.filter.context.FilterContext +import org.radeox.regex.MatchResult +import org.radeox.filter.FilterPipe +import org.radeox.filter.regex.RegexFilter +import org.radeox.filter.MacroFilter +import org.radeox.macro.MacroLoader +import org.radeox.api.engine.context.InitialRenderContext +import org.radeox.filter.* +import org.radeox.util.Encoder +import org.springframework.beans.BeanWrapperImpl +import java.lang.reflect.Field +import org.radeox.api.engine.context.RenderContext + +/** +* @author Graeme Rocher +* @since 1.0 +* +* Created: Feb 19, 2008 +*/ +class GrailsWikiEngine extends BaseRenderEngine implements WikiRenderEngine{ + + static CONTEXT_PATH = "contextPath" + static CACHE = "cache" + + def pageId + + public GrailsWikiEngine(InitialRenderContext initialRenderContext) { + super(initialRenderContext); + } + + public GrailsWikiEngine() { + super(); + } + + public String render(String content, String pageId, RenderContext context) { + this.pageId=pageId + return this.render(content,context); + } + + protected void init() { + if (null == fp) { + FilterPipe localFP = new FilterPipe(initialContext); + + + def filters = [ + ParamFilter, + MacroFilter, + TextileLinkFilter, + LineFilter, + HeaderFilter, + ListFilter, + TableFilter, + StrikeThroughFilter, + NewlineFilter, + ParagraphFilter, + BoldFilter, + SupFilter, + UnderlineFilter, + ItalicFilter, + CodeFilter, + LinkTestFilter, + ImageFilter, + MarkFilter, + KeyFilter, + TypographyFilter, + EscapeFilter + ] + + for(f in filters) { + RegexFilter filter = f.newInstance() + localFP.addFilter(filter) + + if(filter instanceof MacroFilter) { + MacroLoader loader = new MacroLoader() + def repository = filter.getMacroRepository() + //ExcerptMacro + loader.add(repository, new ExcerptMacro()) + loader.add(repository, new WarningMacro()) + loader.add(repository, new NoteMacro()) + loader.add(repository, new InfoMacro()) + loader.add(repository, new AnchorMacro()) + //GroovyMacro + loader.add(repository, new GroovyMacro()) + } + } + localFP.init(); + setFilterPipe localFP + + } + } + + void setFilterPipe(FilterPipe filterPipe) { + Field field = getClass().getSuperclass().getDeclaredField("fp") + field.setAccessible true + field.set(this, filterPipe) + } + + + public boolean exists(String name) { + if(name.startsWith('#')) { + // its an anchor link + return true + } + else if(name.startsWith("http:")||name.startsWith("https:") || name.startsWith("mailto:")) { + return true + } + else { + def cache = initialContext.get(CACHE) + if(cache?.getWikiText(name)) return true + + if(name.indexOf("#")>-1) { + name = name[0..name.indexOf('#')-1] + } + + def page = Page.findByTitle(name)//TODO 外部化 + + return page != null + } + } + + public boolean showCreate() { + return true; + } + + public void appendLink(StringBuffer buffer, String name, String view, String anchor) { + def contextPath = initialContext.get(CONTEXT_PATH) + contextPath = contextPath ?: "" + if(name.startsWith("http:")||name.startsWith("https:")) + buffer << "$view" + else + buffer << "$view" + } + + public void appendLink(StringBuffer buffer, String name, String view) { + + def contextPath = initialContext.get(CONTEXT_PATH) + contextPath = contextPath ?: "" + def decoded = URLDecoder.decode(name, 'utf-8') + + int i =decoded.indexOf('#') + if(decoded.startsWith('#')) { + buffer << "${decoded[1..-1]}" + } + else if(i>-1) { + appendLink(buffer,URLEncoder.encode(decoded[0..i-1],'utf-8'),view, decoded[i+1..-1]) + } + else { + + if(decoded.startsWith("http:")||decoded.startsWith("https:") || decoded.startsWith("mailto:")) + buffer << "$view" + else + buffer << "$view" + } + + } + + public void appendCreateLink(StringBuffer buffer, String name, String view) { + def contextPath = initialContext.get(CONTEXT_PATH) + contextPath = contextPath ?: "." + def addChildTo = (pageId!="0")?"?addChildTo=$pageId":"" + buffer << "$view (add)" + } + +} + +public class WarningMacro extends BaseMacro { + String getName() {"warning"} + void execute(Writer writer, MacroParameter params) { + writer << '
' << params.content << "
" + } +} +public class NoteMacro extends BaseMacro { + String getName() {"note"} + void execute(Writer writer, MacroParameter params) { + writer << '
' << params.content << "
" + } +} + +public class InfoMacro extends BaseMacro { + String getName() {"info"} + void execute(Writer writer, MacroParameter params) { + writer << '
' << params.content << "
" + } +} +//{excerpt} +public class ExcerptMacro extends BaseMacro { + String getName() {"excerpt"} + void execute(Writer writer, MacroParameter params) { + writer << '' + } +} +public class AnchorMacro extends BaseMacro { + + String getName() { "anchor" } + + void execute(Writer writer, MacroParameter params) { + if(params.length >0) { + def name = params.get(0) + def body = params.content ?: '' + writer << "${body}" + } + } + +} + +public class GroovyMacro extends BaseMacro { + String getName() {"groovy"} + void execute(Writer writer, MacroParameter params) { + if(params.content){ + StringWriter out = new StringWriter(); + StringWriter err = new StringWriter(); + PrintWriter stdout = new PrintWriter(out); + PrintWriter stderr = new PrintWriter(err); + + CompilerConfiguration cc = new CompilerConfiguration(); + cc.setOutput(stdout) + cc.setSourceEncoding("utf-8"); + + def b = new Binding(); + b.setVariable("out",stdout) + b.setVariable("err",stderr) + def shell = new GroovyShell(Thread.currentThread().getContextClassLoader(),b,cc) + def script = params.content.replaceAll(">",">") + def result = "" + try { + Script parse = shell.parse(script) + parse.run() + stdout.flush() + result = out.toString() + } catch(Exception e) { + result = "ERROR ${e}" + } finally { + out.close() + err.close() + stdout.close() + stderr.close() + } + writer << "
"
+        writer << result
+        writer << "
" + writer << '
' << params.content << "
" + }else{ + writer <<"" + } + } +} + + +class ItalicFilter extends RegexTokenFilter { + public ItalicFilter() { + super(/_([^\n]*?)_/); + } + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + buffer << " ${result.group(1)} " + } +} +class BoldFilter extends RegexTokenFilter { + public BoldFilter() { + super(/\*([^\n]*?)\*/); + } + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + buffer << "${result.group(1)}" + } +} +class UnderlineFilter extends RegexTokenFilter { + public UnderlineFilter() { + super(/\+([^\n]*?)\+/); + } + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + buffer << "${result.group(1)}" + } +} +class SupFilter extends RegexTokenFilter { + public SupFilter() { + super(/\^([^\n]*?)\^/); + } + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + buffer << "${result.group(1)}" + } +} +class CodeFilter extends RegexTokenFilter { + + public CodeFilter() { + super(/\s@([^\n]*?)@\s/); + } + + + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + def text = result.group(1) + // are we inside a code block? + if(text.indexOf('class="code"') > -1) buffer << "@$text@" + else buffer << "${text}" + } +} +class ImageFilter extends RegexTokenFilter { + public ImageFilter() { + super(/!(\S*?)!/); + } + + + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + + def img = result.group(1) + def path = context.renderContext.get(GrailsWikiEngine.CONTEXT_PATH) ?: "." + + + def image = img.startsWith("http:") ? img : "$path/images/$img" + + buffer << "" + } +} +class LinkTestFilter extends RegexTokenFilter { + + + public LinkTestFilter() { + super(/\[(.*?)\]/) + } + + + public void handleMatch(StringBuffer buffer, MatchResult matchResult, FilterContext filterContext) { + def engine = filterContext.getRenderContext().getRenderEngine() + + + if(engine instanceof WikiRenderEngine) { + GrailsWikiEngine wikiEngine = engine + + + try { + String name = matchResult.group(1) + + int pipeIndex = name.indexOf('|'); + String alias = name; + if (-1 != pipeIndex) { + alias = name.substring(0, pipeIndex); + name = name.substring(pipeIndex + 1); + } + + def link = name.startsWith("http:") ? name : java.net.URLEncoder.encode(name,'utf-8') + + if(wikiEngine.exists(name) || (link.startsWith("http:")||link.startsWith("https:"))) + wikiEngine.appendLink(buffer, link, alias) + else { + wikiEngine.appendCreateLink(buffer, link, alias) + } + } + catch(Exception e) { + println e.message + e.printStackTrace() + } + } + } + +} +class TextileLinkFilter extends RegexTokenFilter { + + public TextileLinkFilter() { + super(/"([^"]+?)":(\S+?)(\s)/); + } + + + public void handleMatch(StringBuffer buffer, MatchResult result, FilterContext context) { + def text = result.group(1) + def link = result.group(2) + def space = result.group(3) + + if(link.startsWith("http://")) { + buffer << "$text$space" + } + else { + buffer << "$text$space" + } + } +} +class HeaderFilter extends RegexTokenFilter{ + + public HeaderFilter() { + super(/(?m)^h(\d)\.\s+?(.*?)$/); + } + + + public void handleMatch(StringBuffer out, MatchResult matchResult, FilterContext filterContext) { + + def header = matchResult.group(1) + def content = matchResult.group(2) + def contentNoTags = content?.replaceAll(/(m?)<\/?[^>]+>/,'') + + out << "$content" + } + + +} +class TableFilter extends RegexTokenFilter { + + public TableFilter() { + super(/(?m)^(\|(.|\n)+[\|\n|\|])/) + } + + public void handleMatch(StringBuffer stringBuffer, MatchResult matchResult, FilterContext filterContext) { + def result = matchResult.group(1) + def colCount = 0 + result.eachLine{ + def current = it.trim() + if(current.startsWith("||")) { + if(colCount==0) stringBuffer << '' + stringBuffer << '' + def tokens = current.split(/\|\|/) + for(t in tokens ) { + if(t!="") stringBuffer << "" + } + stringBuffer << '' + colCount++ + } + else if(current.startsWith("|")) { + if(colCount==0) stringBuffer << '
${t}
' + def tokens = current.split(/\|/) + stringBuffer << '' + for(t in tokens ) { + if(t!="") stringBuffer << "" + } + stringBuffer << '' + colCount++ + } + + if(!current.startsWith("|")){ + colCount=0 + stringBuffer << '
${t}
' + stringBuffer << "$it\n" + } + } + stringBuffer << '' + + } + + +} \ No newline at end of file diff --git a/src/groovy/org/jggug/wiki/GrailsWikiEngineFactoryBean.groovy b/src/groovy/org/jggug/wiki/GrailsWikiEngineFactoryBean.groovy new file mode 100644 index 0000000..c071a08 --- /dev/null +++ b/src/groovy/org/jggug/wiki/GrailsWikiEngineFactoryBean.groovy @@ -0,0 +1,31 @@ +package org.jggug.wiki + +import org.springframework.beans.factory.FactoryBean +import org.radeox.engine.context.BaseInitialRenderContext +//import org.grails.cache.CacheService +import org.springframework.beans.factory.InitializingBean + + +class GrailsWikiEngineFactoryBean implements FactoryBean, InitializingBean { + + def context = new BaseInitialRenderContext(); + + CacheService cacheService + String contextPath + + private engine + + public Object getObject() { engine } + + Class getObjectType() { GrailsWikiEngine } + + boolean isSingleton() { true } + + public void afterPropertiesSet() { + context.set(GrailsWikiEngine.CONTEXT_PATH, contextPath) + context.set(GrailsWikiEngine.CACHE, cacheService) + engine = new GrailsWikiEngine(context) + context.setRenderEngine engine + + } +} \ No newline at end of file