Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Markdown processing in Coldfusion 9 with tables extension support

I'm attempting to get Markdown with the tables extension working on Coldfusion 9. There are a few other similar questions regarding CF and Markdown here on stackoverflow but none of them deal with extensions.

So far I have tried;

  • markdownj via javaloader.cfc
  • pegdown via javaloader.cfc
  • showdownjs via orangepips nice cfc (I would link all these but don't have the rep for more than 2)

All of which work fine for basic Markdown but none of which have tables support out of the box.
Both pegdown and showdown.js support the tables extension. Markdownj however doesn't look to support it at present but I thought it worth a try.

I think that my problem is in getting the syntax correct for loading the extension in either pegdown or showdown. Both work quite differently, one being pure Java and the other being interpreted Javascript.

For pegdown

My code here is very simple and just uses javaloader to load both pegdown and it's required parboiled library. This part appears works fine with no errors but when I try to use the pegdown class I get a fairly generic error;

An exception occurred while instantiating a Java object. The class must not be an interface or an abstract class. Error: ''. 

The code for calling pegdown;

<cfscript>
    jClass = [
        "#getDirectoryFromPath(getCurrentTemplatePath())#/pegdown/pegdown-1.2.1.jar"
        ,   "#getDirectoryFromPath(getCurrentTemplatePath())#/pegdown/parboiled-core-1.1.3.jar"
        ];
    javaloader      = createObject('component','components.javaloader.JavaLoader').init(jClass, true);

    variables.pegdown = javaloader.create("org.pegdown.PegDownProcessor");
</cfscript>
<cfdump var="#variables.pegdown#" />

For showdownjs:

I've tried adding the extension file (extensions/table.js) to the var for evaluation and adding the extensions var to the converter options as per the documentation but it doesn't work. I'm guessing that showdown.js isn't expecting to be running inside the underlying Java scriptEngineManager as the main showdown.js script can't "see" the tables extension, failing at line 246 with;

The script had an error: sun.org.mozilla.javascript.internal.JavaScriptException: Extension 'undefined' could not be loaded. It was either not found or is not a valid extension. (#246) in at line number 246 

My code for showdown.js is based on Orangepips answer in the linked question above.

<cfcomponent output="false" accessors="true">
    <cffunction name="init" output="false" access="public" returntype="Showdown" hint="Constructor">
        <cfset variables.manager = createObject("java", "javax.script.ScriptEngineManager").init()>
        <cfset variables.engine = manager.getEngineByName("javascript")>
        <cfreturn this/>
    </cffunction>

    <cffunction name="toHTML" output="false" access="public" returntype="any" hint="">
        <cfargument name="markdownText" type="string" required="true"/>
        <cfset var local = structNew()/>
        <cfset var bindings = variables.engine.createBindings()>
        <cfset var result = "">
        <cfset var showdownJS = "" />

        <cftry>
            <cfset bindings.put("markdownText", arguments.markdownText)>
            <cfset variables.engine.setBindings(bindings, createObject("java", "javax.script.ScriptContext").ENGINE_SCOPE)>
            <cfset showdownJS &= fileRead('#getDirectoryFromPath(getCurrentTemplatePath())#/showdown.js')>
            <cfset showdownJS &= fileRead('#getDirectoryFromPath(getCurrentTemplatePath())#/extensions/table.js')>
            <cfset showdownJS &= showdownAdapterJS()>
            <cfset result = engine.eval(showdownJS)>
            <cfcatch type="javax.script.ScriptException">
                <cfset result = "The script had an error: " & cfcatch.Message>
            </cfcatch>
        </cftry>

        <cfreturn result>
    </cffunction>

    <cffunction name="showdownAdapterJS" output="false" access="private" returntype="string" hint="">
        <cfset var local = structNew()/>
<cfsavecontent variable="local.javascript">
<cfoutput>#chr(13)##chr(10)#
var __converter = new Showdown.converter({extensions:['table']});
__converter.makeHtml(markdownText);</cfoutput>
</cfsavecontent>
        <cfreturn local.javascript>
    </cffunction>
</cfcomponent>

I'm open to any ideas and don't have any particular preference for one solution over another.

stacktrace

java.lang.ClassNotFoundException: org.parboiled.BaseParser at 
coldfusion.bootstrap.BootstrapClassLoader.loadClass(BootstrapClassLoader.java:235) at 
java.lang.ClassLoader.loadClass(Unknown Source) at 
com.compoundtheory.classloader.NetworkClassLoader.loadClass(NetworkClassLoader.java:463) at 
java.lang.ClassLoader.loadClass(Unknown Source) at 
java.lang.ClassLoader.loadClassInternal(Unknown Source) at 
java.lang.ClassLoader.defineClass1(Native Method) at 
java.lang.ClassLoader.defineClass(Unknown Source) at 
java.lang.ClassLoader.defineClass(Unknown Source) at 
com.compoundtheory.classloader.NetworkClassLoader.loadClass(NetworkClassLoader.java:450) at 
java.lang.ClassLoader.loadClass(Unknown Source) at 
java.lang.ClassLoader.loadClassInternal(Unknown Source) at 
java.lang.Class.getDeclaredFields0(Native Method) at 
java.lang.Class.privateGetDeclaredFields(Unknown Source) at 
java.lang.Class.privateGetPublicFields(Unknown Source) at 
java.lang.Class.getFields(Unknown Source) at 
coldfusion.runtime.java.ObjectHandler.Initialize(ObjectHandler.java:35) at 
coldfusion.runtime.java.ObjectHandler.<init>(ObjectHandler.java:30) at 
coldfusion.runtime.java.ReflectionCache$1.fetch(ReflectionCache.java:29) at 
coldfusion.util.SoftCache.get_statsOff(SoftCache.java:133) at 
coldfusion.util.SoftCache.get(SoftCache.java:81) at 
coldfusion.runtime.java.ReflectionCache.get(ReflectionCache.java:36) at 
coldfusion.runtime.java.JavaProxy.<init>(JavaProxy.java:35) at 
sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at 
sun.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at 
sun.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at 
java.lang.reflect.Constructor.newInstance(Unknown Source) at 
coldfusion.runtime.java.JavaProxy.CreateObject(JavaProxy.java:166) at 
coldfusion.runtime.java.JavaProxy.invoke(JavaProxy.java:80) at 
coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2360) at 
cfJavaLoader2ecfc535209679$funcCREATEJAVAPROXY.runFunction(/srv/vhosts/myproject/httpdocs/components/javaloader/JavaLoader.cfc:329) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:472) at 
coldfusion.filter.SilentFilter.invoke(SilentFilter.java:47) at 
coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:405) at 
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:368) at 
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:55) at 
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:321) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:220) at 
coldfusion.runtime.CfJspPage._invokeUDF(CfJspPage.java:2582) at 
cfJavaLoader2ecfc535209679$funcCREATE.runFunction(/srv/vhosts/myproject/httpdocs/components/javaloader/JavaLoader.cfc:87) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:472) at 
coldfusion.filter.SilentFilter.invoke(SilentFilter.java:47) at 
coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:405) at 
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:368) at 
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:55) at 
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:321) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:220) at 
coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:491) at 
coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:337) at 
coldfusion.runtime.CfJspPage._invoke(CfJspPage.java:2360) at 
cfpegdown2ecfm1473046932.runPage(/srv/vhosts/myproject/httpdocs/_temp/markdown/pegdown.cfm:22) at 
coldfusion.runtime.CfJspPage.invoke(CfJspPage.java:231) at 
coldfusion.tagext.lang.IncludeTag.doStartTag(IncludeTag.java:416) at 
coldfusion.runtime.CfJspPage._emptyTcfTag(CfJspPage.java:2722) at 
cfApplication2ecfc294205112$funcONREQUEST.runFunction(/srv/vhosts/myproject/httpdocs/Application.cfc:377) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:472) at 
coldfusion.runtime.UDFMethod$ReturnTypeFilter.invoke(UDFMethod.java:405) at 
coldfusion.runtime.UDFMethod$ArgumentCollectionFilter.invoke(UDFMethod.java:368) at 
coldfusion.filter.FunctionAccessFilter.invoke(FunctionAccessFilter.java:55) at 
coldfusion.runtime.UDFMethod.runFilterChain(UDFMethod.java:321) at 
coldfusion.runtime.UDFMethod.invoke(UDFMethod.java:220) at 
coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:491) at 
coldfusion.runtime.TemplateProxy.invoke(TemplateProxy.java:337) at 
coldfusion.runtime.AppEventInvoker.invoke(AppEventInvoker.java:88) at 
coldfusion.runtime.AppEventInvoker.onRequest(AppEventInvoker.java:280) at 
coldfusion.filter.ApplicationFilter.invoke(ApplicationFilter.java:356) at 
coldfusion.filter.RequestMonitorFilter.invoke(RequestMonitorFilter.java:48) at 
coldfusion.filter.MonitoringFilter.invoke(MonitoringFilter.java:40) at 
coldfusion.filter.PathFilter.invoke(PathFilter.java:94) at 
coldfusion.filter.ExceptionFilter.invoke(ExceptionFilter.java:70) at 
coldfusion.filter.BrowserDebugFilter.invoke(BrowserDebugFilter.java:79) at 
coldfusion.filter.ClientScopePersistenceFilter.invoke(ClientScopePersistenceFilter.java:28) at 
coldfusion.filter.BrowserFilter.invoke(BrowserFilter.java:38) at 
coldfusion.filter.NoCacheFilter.invoke(NoCacheFilter.java:46) at 
coldfusion.filter.GlobalsFilter.invoke(GlobalsFilter.java:38) at 
coldfusion.filter.DatasourceFilter.invoke(DatasourceFilter.java:22) at 
coldfusion.filter.CachingFilter.invoke(CachingFilter.java:62) at 
coldfusion.CfmServlet.service(CfmServlet.java:200) at 
coldfusion.bootstrap.BootstrapServlet.service(BootstrapServlet.java:89) at 
jrun.servlet.FilterChain.doFilter(FilterChain.java:86) at 
com.intergral.fusionreactor.filter.FusionReactorCoreFilter.doRequestNoFilter(FusionReactorCoreFilter.java:712) at 
com.intergral.fusionreactor.filter.FusionReactorCoreFilter.doFusionRequest(FusionReactorCoreFilter.java:341) at 
com.intergral.fusionreactor.filter.FusionReactorCoreFilter.doFilter(FusionReactorCoreFilter.java:246) at 
com.intergral.fusionreactor.filter.FusionReactorFilter.doFilter(FusionReactorFilter.java:121) at 
jrun.servlet.FilterChain.doFilter(FilterChain.java:94) at 
coldfusion.monitor.event.MonitoringServletFilter.doFilter(MonitoringServletFilter.java:42) at 
coldfusion.bootstrap.BootstrapFilter.doFilter(BootstrapFilter.java:46) at 
jrun.servlet.FilterChain.doFilter(FilterChain.java:94) at 
jrun.servlet.FilterChain.service(FilterChain.java:101) at 
jrun.servlet.ServletInvoker.invoke(ServletInvoker.java:106) at 
jrun.servlet.JRunInvokerChain.invokeNext(JRunInvokerChain.java:42) at 
jrun.servlet.JRunRequestDispatcher.invoke(JRunRequestDispatcher.java:286) at 
jrun.servlet.ServletEngineService.dispatch(ServletEngineService.java:543) at 
jrun.servlet.jrpp.JRunProxyService.invokeRunnable(JRunProxyService.java:203) at 
jrunx.scheduler.ThreadPool$ThreadThrottle.invokeRunnable(ThreadPool.java:428) at 
jrunx.scheduler.WorkerThread.run(WorkerThread.java:66) 

Update

This is now working thanks to barnyr.
The problem stems from my lack of understanding of Java and the some additional dependancies for PegDown and Parboiled.

Parboiled needs both the java and core .jars of Pegdown. Pegdown expects to find the ASM library
I used version 4.1 and included the -all- version. The recommendation is to only include the necessary asm jars but for now this is enough for me to take this further.

Working code for CF9 (Running on JRUN/Linux)

<!--- Load some demo markdown content --->
<cfset markdownString = fileRead("#getDirectoryFromPath(getCurrentTemplatePath())#/demo.txt")>

<!--- Directory containing all the necessary jar files. --->
<cfset jarDir = "#getDirectoryFromPath(getCurrentTemplatePath())#pegdown" />

<!--- Array of necessary classes --->
<cfset jClass = [
        "#jarDir#/parboiled-java-1.1.3.jar"
        ,   "#jarDir#/asm-all-4.1.jar"
        ,   "#jarDir#/parboiled-core-1.1.3.jar"
        , "#jarDir#/pegdown-1.2.1.jar"
        ] />
<cfset javaloader = createObject('component','components.javaloader.JavaLoader').init(jClass, false) />

<!--- Hex values for different extensions can be found in org.pegdown.Extensions.java (0x20 is for tables support) --->
<cfset variables.pegdown = javaloader.create("org.pegdown.PegDownProcessor").init(javaCast("int", InputBaseN("0x20", 16))) />

<!--- Output the HTML conversion --->
<cfoutput>#variables.pegdown.markdownToHtml(markdownString)#</cfoutput>
like image 909
Matt Casey Avatar asked Mar 18 '13 17:03

Matt Casey


1 Answers

You're getting the error because of the way that PegDown's dependency - parboiled is distributed.

It's a scala project and it produces more than one JAR. The parboiled core jar you have appears to have just the core algorithms and the Scala language API.

To use the Java API, you'll also need to get hold of the parboiled-java library which provides the bindings (including the BaseParser class mentioned in the exception above). The GitHub repo only has the 1.1.4 files, but there's a copy of the 1.1.3 jar here: http://mirrors.ibiblio.org/maven2/org/parboiled/parboiled-java/1.1.3/parboiled-java-1.1.3.jar

If you download that and add it to the array of jar files you're handing to JavaLoader you should be OK.

like image 166
barnyr Avatar answered Nov 09 '22 17:11

barnyr