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 << ''
+ colCount++
+ }
+ else if(current.startsWith("|")) {
+ if(colCount==0) stringBuffer << ''
+ def tokens = current.split(/\|/)
+ stringBuffer << ''
+ for(t in tokens ) {
+ if(t!="") stringBuffer << "${t} | "
+ }
+ stringBuffer << '
'
+ colCount++
+ }
+
+ if(!current.startsWith("|")){
+ colCount=0
+ stringBuffer << '
'
+ 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