Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementating Showdown.js markdown parser on the server side for ColdFusion

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:

  1. As of now there are no ColdFusion markdown parsers for this specific purpose
  2. Using Showdown.js on the client-side, and then a different parser on the server-side will result in inconsistent markup between the preview displayed to the client and the version stored in the database. Given that markdown is loosely defined, most parser implementations will have subtle differences.

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

like image 379
Mohamad Avatar asked Jan 05 '11 03:01

Mohamad


1 Answers

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>
like image 122
orangepips Avatar answered Oct 07 '22 18:10

orangepips