Problem StatementCSS-based colorer?Colorer on sourceforge.netBack to CSS-based colorerWow, this is an SOA and AJAX :)New behaviorTest FirstColorer-Bridge ImplementationP.S.Problem StatementWhen working on the
article about my journey with WebDav technology I faced with a challenge to colorize code samples that I wanted to share. I wanted that the source code in my article be not only shadowed gray and printed with Courier font, but really presented the way you would expect it to be presented by an IDE.
I saw a few development forums doing this behind the scenes and I thought I wanted the same capability on my blog. The challenge is that my blog page is hosted on Blogger so server-side solution doesn’t work. Unless Blogger provides custom tags to do this, that is :). The only thing I’m allowed to do is to customize HTML of my postings.
CSS-based colorer?I was thinking how great it would be to have a CSS-based solution so you can put your source code into HTML <SPAN> element, define a few style attributes, and let it automagically colorize everything. wow… In theory, you could build a language recognizer in JavaScript (port
ANTLR), add some functions around it to convert Java-Java into HTML-Java, wrap it into
DHTML behavior, and here you go. Your colorer CSS style is ready for you. The only thing is, it would take me months (or years) to cope with it :) I knew this is not an option so I started as I always start when I want to see what’s out there – Google search. I was hoping to find a colorer library with a command line interface or a Java API so I could run it or build a small program to colorize my samples offline and upload the result to my blog. This sounded much more realistic :)
Colorer on sourceforge.netFirst search result brought me to the
http://colorer.sourceforge.net. This was exactly what I was looking for and much more. Colorer guys created web interface where you can colorize your code fragments on the fly. This was the kicker ! Go to
http://colorer.sourceforge.net/php and check it out.
Compare this:
public static String doSomething(final Integer value) {
throw new Exception("sample");
}
To this:
public static String doSomething(final Integer value) {
throw new Exception("sample");
}
Or this:
<xsl:template match="/">
<xsl:apply-templates select="node/element/@myAttr"/>
</xsl:template>
To this:
<xsl:template match="/">
<xsl:apply-templates select="node/element/@myAttr"/>
</xsl:template>
Back to CSS-based colorercolorer-take5 solved my original problem. Now I had all my code samples colorized and nice-looking. But what about CSS-based colorer? I still wanted to make one. And now I knew how to do this :) The solution presented is really a toy that I don’t believe is useful but it works well and it seems to be very elegant.
Wow, this is an SOA and AJAX :)I decided to use colorer web interface as a service provider. I needed to build a capability to call it, process results it returns, and wrap it into the DHTML behavior so that an HTML element can consume the service and colorize its content.
Colorer service API is just a form POST to http://colorer.sourceforge.net/php/generator.php with number of parameters. Now we need that the HTML element with some content to colorize defines new behavior that would let it consume the colorer service,
New behaviorHTML element powered by the new colorer-bridge behavior should define a few custom properties:
- language – to tell colorer what type of language grammar to recognize
- encoding – since colorer-take5 supports input and output encoding it’s a good idea to allow using it
- style – to be able to use multiple color schemas supported by colorer-take5
- mode – to define whether element should colorize itself automatically or defer this until external command to colorize received
When properties defined, the element needs to be granted with actions to actually call the colorer – colorize() - and parse the results – applyColorerResults(). Looks like we are all set to wrap it all together.
Test FirstLet’s look at the HTML sample that uses the colorer-bridge component. It will be much easier to comprehend the idea by looking at how it’s used:
<html xmlns:c>
<?IMPORT namespace="c" implementation="colorer-bridge.htc"?>
<head>
<title>Colorer API Bridge Sample</title>
</head>
<body>
<!-- used as element behavior -->
<c:colorer sourceLang="jScript">
<pre>
function myFunction() {
this.property = value;
}
</pre>
</c:colorer>
<!-- another technique knows as attached behaviour -->
<pre style="behavior:url('colorer-bridge.htc')" sourceLang="xml">
<root>
<element attribute="value"/>
</root>
</pre>
</body>
</html>
As you see you can define your custom <c:colorer> element or attach new behavior to the <PRE> element. The reason we need PRE anyway is to preserve whitespaces and line breaks. Otherwise Explorer consumes everything before the component can grab it.
Colorer-Bridge ImplementationOk. And this is our guy. The colorer-bridge.htc that does the job:
<PUBLIC:COMPONENT tagName="colorer">
<PUBLIC:PROPERTY ID="propLanguage" NAME="sourceLang"
GET="getLanguage" PUT="setLanguage"/>
<PUBLIC:PROPERTY ID="propEncodingIn" NAME="encodingIn"
GET="getEncodingIn" PUT="setEncodingIn"/>
<PUBLIC:PROPERTY ID="propEncodingOut" NAME="encodingOut"
GET="getEncodingOut" PUT="setEncodingOut"/>
<PUBLIC:PROPERTY ID="propStyle" NAME="style"
GET="getStyle" PUT="setStyle"/>
<PUBLIC:PROPERTY ID="propMode" NAME="mode"
GET="getMode" PUT="setMode"/>
<PUBLIC:ATTACH EVENT="oncontentready"
FOR="element" ONEVENT="doInit()" />
<PUBLIC:METHOD NAME="colorize" />
<PUBLIC:METHOD NAME="rollback" />
<SCRIPT>
var sWaitMessage = "Please whait while colorizing...";
var sApiUrl = "http://colorer.sourceforge.net/php/generator.php";
var sOriginalContent; // code to colorize
var oCodeSnippetContainer; // HTML element with the code
var oColorerAPI; // XmlHttpRequest object
function doInit() {
// see if applied to a PRE element
if (element.tagName == 'PRE') {
oCodeSnippetContainer = element;
// or if PRE element defined as a child
} else {
oCodeSnippetContainer =
element.getElementsByTagName("PRE")[0];
}
// remember original content to support rollback
sOriginalContent = oCodeSnippetContainer.innerHTML;
oCodeSnippetContainer.innerText = sWaitMessage;
// initialize Colorer 'API'
oColorerAPI = new ActiveXObject("Microsoft.XMLHTTP");
oColorerAPI.open("POST", sApiUrl, true);
oColorerAPI.onreadystatechange = applyColorerResponse;
if (element.mode == "auto") {
colorize();
}
}
function colorize() {
// put together POST request
var sRequest = "";
sRequest += "i_encoding=" + element.encodingIn;
sRequest += "&o_encoding=" + element.encodingOut;
sRequest += "&hrd_color=" + element.style;
sRequest += "&type=" + element.sourceLang;
sRequest += "&file_content=" + sOriginalContent;
oColorerAPI.setRequestHeader("Content-Type",
"application/x-www-form-urlencoded");
oColorerAPI.send(sRequest);
}
function applyColorerResponse() {
if (oColorerAPI.readyState == 4 && oColorerAPI.status == 200) {
// replace original code fragment with its colorized version
oCodeSnippetContainer.innerHTML = oColorerAPI.responseText;
}
}
function rollback() {
oCodeSnippetContainer.innerHTML = sOriginalContent;
}
//----------------- PROPERTIES --------------------
var sLanguage = ""; // Autodetect
var sEncodingIn = "ISO-8859-1";
var sEncodingOut = "ISO-8859-1";
var sStyle = "default"; // white
var sMode = "auto";
function getLanguage() {
return sLanguage;
}
function setLanguage(sValue) {
sLanguage = sValue;
propLanguage.fireChange();
}
// other getters and setters skipped
</SCRIPT>
<BODY>
</BODY>
</PUBLIC:COMPONENT>
That’s it. Enjoy!
P.S.P.S. Many thanks to the Colorer team @ sourceforge.
P.P.S. Do you know the name of the guy who renamed DHTML into AJAX?