This is a "fact finding" question to see how difficult it would be to create a ColdFusion UDF to parse markdown on the server using the showdown.js parser. There is already a java implementation that utilizes showdown.js (see code at the end of this post) and I want to see how to go about implementing it for ColdFusion. I have no experience in Java and I would not particularly call myself "a programmer," but I don't want this to stop me from trying.
Summary
I would like to run Shadown.js server-side in order to convert markdown to HTML.
Why?
Saving two versions of a user entry, one in markdown format and another in HTML, allows us to display the raw markdown version to the end user in case they wanted to edit their entry.
Why not use a server-side parser?
For two reasons:
There is a very good blog entry that discusses the issue.
Why not do all the parsing on the client-side and post both versions?
This does not strike me as a secure solution. I also think users would potentially be able to post markdown with HTML that does not match.
Are there any existing implementations?
There is one implementation called CFShowdown, but it's not for this specific purpose. Rather, it's for handling output on a page. The comments section of the aforementioned blog features a pure Java implementation written by a user called David:
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine jsEngine = manager.getEngineByName("js");
try
{
jsEngine.eval(new InputStreamReader(getClass().getResourceAsStream("showdown.js")));
showdownConverter = jsEngine.eval("new Showdown.converter()");
}
catch (Exception e)
{
log.error("could not create showdown converter", e);
}
try
{
return ((Invocable) jsEngine).invokeMethod(
showdownConverter,
"makeHtml",
markdownString
) + "";
}
catch (Exception e)
{
log.error("error while converting markdown to html", e);
return "[could not convert input]";
}
Objective
Create a java class that would allow us to use this implementation with a ColdFusion UDF or a custom tag inside a component, something along the lines of <cfset html = getMarkdown(string)>
Since I have no experience with Java, I want to get some advice and input from users on where and how to start going about this task. I created a
Have files showdown.js and a file markdown.txt (example below) in the same directory.
showdown.cfm
<cfscript>
manager = createObject("java", "javax.script.ScriptEngineManager").init();
jsEngine = manager.getEngineByName("js");
showdownJS = fileRead('#getDirectoryFromPath(getCurrentTemplatePath())#/showdown.js');
jsEngine.eval(showdownJS);
showdownConverter = jsEngine.eval("new Showdown.converter()");
markdownString = fileRead("#getDirectoryFromPath(getCurrentTemplatePath())#/markdown.txt");
args = [markdownString];
result = jsEngine.invokeMethod(
showdownConverter,
"makeHtml",
args
) & "";
</cfscript>
markdown.txt
Showdown Demo
-------------
You can try out Showdown on this page:
- Type some [Markdown] text on the left side.
- See the corresponding HTML on the right.
For a Markdown cheat-sheet, switch the right-hand window from *Preview* to *Syntax Guide*.
Showdown is a JavaScript port of the original Perl version of Markdown. You can get the full [source code] by clicking on the version number at the bottom of the page.
Also check out [WMD, the Wysiwym Markdown Editor][wmd]. It'll be open source soon; email me at the address below if you'd like to help me test the standalone version.
**Start with a [blank page] or edit this document in the left window.**
[Markdown]: http://daringfireball.net/projects/markdown/
[source code]: http://attacklab.net/showdown/showdown-v0.9.zip
[wmd]: http://wmd-editor.com/
[blank page]: ?blank=1 "Clear all text"
Update
Here's a version that takes Adam Presley's work in Java and does it all in a CFC. Note I took that little bit of magic he added at the end of showdown.js and put it into a CFC function whose return value is appended (i.e. showdownAdapterJS()
).
CFC
<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 = "">
<cftry>
<cfset bindings.put("markdownText", arguments.markdownText)>
<cfset variables.engine.setBindings(bindings, createObject("java", "javax.script.ScriptContext").ENGINE_SCOPE)>
<cfset var showdownJS = fileRead('#getDirectoryFromPath(getCurrentTemplatePath())#/showdown.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();
__converter.makeHtml(markdownText);</cfoutput>
</cfsavecontent>
<cfreturn local.javascript>
</cffunction>
</cfcomponent>
Usage
<cfset showdown = createObject("component", "Showdown").init()>
<cfset markdownString = fileRead("#getDirectoryFromPath(getCurrentTemplatePath())#/markdown.txt")>
<cfoutput>#showdown.toHTML(markdownString)#</cfoutput>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With