Monday 2 November 2009

A customizable syntax highlighter for your blogs

Today I wish to share with you the results of my latest endeavor - a customizable syntax highlighter for use in programming blogs. You will now be able to paste plain code into your HTML and let the colorizer do the hard work of highlighting keywords, strings, comments etc. in your code. The colorization happens at runtime on the client side. I am releasing the code under the Creative Commons Attribution 3.0 Unported license Just mention somewhere in your blog that you are using my code (a link to my blog will be wonderful).

Salient features
  • Supports colorization of C#, C++, Java, JavaScript, VBScript
  • Can be extended easily to provide colorization for other languages
  • The colorization/parsing happens on the client side. You can post plain code not encumbered by embedded HTML tags in any way.

How to use

1. Paste the following code in the head section of your HTML markup. For blogspot you need to edit the XML file representing the page layout. Goto Blogger->Layout->Edit HTML. Download the XML file. Update the head section and upload your changes.

<link href='http://tinyurl.com/yd6kzwq' rel='stylesheet' type='text/css'/>

<script src='http://tinyurl.com/y87zt6f' type='text/javascript'/>

<script type='text/javascript'>
function run_colorizer()
{
var preTags = document.getElementsByTagName("pre");
var colorizer = new Colorizer(preTags);
colorizer.Run();
}
</script>

2. Add an onload event handler to your body tag...

<body onload='run_colorizer()'>

NOTE: The above two steps need to be done only once.

3. Surround your code with the pre tag; also indicate the language as follows

<pre class="cppcode">
// WSHTest.cpp : Defines the entry point for the console application.
//

#import "progid:Wscript.Shell" no_namespace \
rename("FreeSpace", "WshFreeSpace") \
rename("ExpandEnvironmentStrings", "WshExpandEnvironmentStrings") \
rename("AddPrinterConnection", "WshAddPrinterConnection") \
rename("SetDefaultPrinter", "WshSetDefaultPrinter") \
rename("DeleteFile", "WshDeleteFile") \
rename("MoveFile", "WshMoveFile") \
rename("CopyFile", "WshCopyFile")


int main()
{
return 0;
}
</pre>


Here is how your readers will see it...

// WSHTest.cpp : Defines the entry point for the console application.
//

#import "progid:Wscript.Shell" no_namespace \
rename("FreeSpace", "WshFreeSpace") \
rename("ExpandEnvironmentStrings", "WshExpandEnvironmentStrings") \
rename("AddPrinterConnection", "WshAddPrinterConnection") \
rename("SetDefaultPrinter", "WshSetDefaultPrinter") \
rename("DeleteFile", "WshDeleteFile") \
rename("MoveFile", "WshMoveFile") \
rename("CopyFile", "WshCopyFile") 


int main()
{
return 0;
}

NOTE: Languages available as of now are ...
1. jscode - JavaScript
2. cppcode - C++
3. csharpcode - C#
4. vbscriptcode - VBScript //Incomplete
5. javacode - Java

Checkout other entries in this blog to see some more colorful code.
That's all for now. In the next part of this entry I will show you how you can extend the colorizer for your favourite language.

UPDATE
I have updated the code to handle some Internet Explorer quirks. It should now work with
1. IE 6
2. Firefox 3.5.4
3. Chrome 3.0.195.27

I Haven't tested with other browsers or other versions of the above browsers for that matter but I think this covers a lot of ground. But still if somebody does find a fringe case where the Colorizer stops working then please do let me know.

Saturday 31 October 2009

JavaScript / VBScript string interpolation

JavaScript (JScript) and VBScript are two pretty nifty programming / automation tools on the windows platform. You can run JS / VBS programs on a plain vanilla Windows XP (or above) PC without installing anything. They can be used for a variety of purposes from automating MS-WORD, EXCEL, Internet explorer etc to doing basic everyday tasks like copying files and folders programmatically. But if you have used them for any length of time then I think you will agree that these languages are not feature complete by any means. But this does not mean that you cannot improvise! Here is my own take on providing formatted string output support for JavaScript and VBScript. Enjoy!


JScript
String.prototype.Trim = function() {
    return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "");;
}


function sprint(str)
{
    var re = /\$(?:\{(.*?)\}|(\w[\w\d]*))/g; //The ? makes the * non-greedy

    var arr;
    var retVal = "";
    var lastIndex = 0;
    
    while ((arr = re.exec(str)) != null)
    {
       retVal += str.substring(lastIndex, arr.index);
       var expr = "";
       for(j=1; j<arr.length; j++)
       {
            if(arr[j].Trim() != "")
            {
                expr = arr[j].Trim();
                break;
            }
       }
       retVal += eval(expr);
       lastIndex = arr.lastIndex;
    }
    
    //Note using substr below and not substring
    retVal += str.substr(lastIndex);
    return retVal;
}


function print(str)
{
    WScript.Echo(sprint(str))
}

/////////////////  USAGE  ///////////////
var greeting  = "Hello"
var firstName = "SDX"
var lastName  = "2000"

var i=1

print("$greeting $firstName ${lastName}!")
print("3+5=${3+5}")
print("Square root of 81 is ${   Math.sqrt(81)   }")
print("${i = i+1}") //increaments i
print("${i == i+1}") //does a comparison
//NOTE: WScript.Echo does not return anything hence you can see an "undefined"
//in the output
print("${WScript.Echo(greeting +\" \" + firstName +\" \" + lastName)}")

Output

Hello SDX 2000!
3+5=8
Square root of 81 is 9
2
false
Hello SDX 2000
undefined


VBScript
option explicit

dim firstName, lastName, greeting


function sprint(str)
    Dim regEx, matches, match, subMatch, smatches, i, expr
    set regEx = new RegExp
    regEx.IgnoreCase = true ' This is VBScript after all
    regEx.Global = true
    regEx.Pattern = "\$(?:\{(.*?)\}|(\w[\w\d]*))" 'The ? makes the * non-greedy
    
    set matches = regEx.Execute(str)
    
    for each match in matches
        for i=0 to match.SubMatches.Count-1 'Unable to 'for each' over SubMatches for some reason
            if trim(match.SubMatches(i)) <> "" then
                expr = trim(match.SubMatches(i))
            end if
        next
        
        str = replace(str, Match.Value, eval(expr)) 'Note this will replace all instances of the expression
    next
    sprint = str
end function

sub print(str)
    WScript.Echo sprint(str)
end sub

' *************  USAGE  ************
greeting  = "Hello"
firstName = "SDX"
lastName  = "2000"

dim i
i=1

print "$greeting $firstName ${LastName}!"
print "3+5=${3+5}"
print "Square root of 81 is ${sqr(81)}"
print "${i = i+1}" 'Prints false; does not increment i
print "${msgbox(firstName & lastName,0,greeting)}"

Output

Hello SDX 2000!
3+5=8
Square root of 81 is 9
False
1

Thursday 29 October 2009

Windows messages for workstation lock/unlock etc events

Have you ever wanted to write a program which could take some action based on common user activities like logging on and off, locking or unlocking the computer etc. If yes then what you need is WM_WTSSESSION_CHANGE. Its a windows message which can be intercepted to determine if the user has locked the workstation and gone for lunch, among other things.

But before you run off to code your latest earth shattering application you need to know that you will not receive this message in your windows message queue unless you opt in i.e. you need to call WTSRegisterSessionNotification and tell windows that you are interested in receiving these messages.

Some applications which I can think of off the top of my head are...
1. Write an app to change the status message of your favorite chat client
2. Maintain a record of user login/logout/lock/unlock times

Please feel free to chip in with your own ideas.

Bye for now.

Wednesday 28 October 2009

Using WScript.Shell from VC++ Part - I

The WScript.Shell component provides some much sought after functionality. It provides some easy to use APIs for doing things which often require convoluted code in plain VC++. For example creating a windows shortcut is a simple matter of calling IWshShell::CreateShortcut()! Can it get any easier? (please feel free to let me know if it does :) )

But naively trying to import the WScript.Shell library into your project will expose you to a world of hurt. Here's a small preview...

wshtest.cpp(11) : warning C4278: 'ExpandEnvironmentStrings': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
wshtest.cpp(11) : warning C4278: 'AddPrinterConnection': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
wshtest.cpp(11) : warning C4278: 'SetDefaultPrinter': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
wshtest.cpp(11) : warning C4278: 'DeleteFile': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
wshtest.cpp(11) : warning C4278: 'MoveFile': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
wshtest.cpp(11) : warning C4278: 'CopyFile': identifier in type library 'Wscript.Shell' is already a macro; use the 'rename' qualifier
debug\wshom.tlh(1159) : warning C4003: not enough actual parameters for macro 'GetFreeSpace'
debug\wshom.tlh(1159) : error C2059: syntax error : 'constant'
debug\wshom.tlh(1159) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
debug\wshom.tlh(1159) : warning C4183: '_variant_t': missing return type; assumed to be a member function returning 'int'
debug\wshom.tlh(1160) : error C2146: syntax error : missing ';' before identifier 'GetTotalSize'
debug\wshom.tlh(1160) : error C4430: missing type specifier - int assumed. Note: C++ does not support default-int


Ok now getting to what you may be most interested in, here's the code which manages to import the WScript.Shell library and still compile...


// WSHTest.cpp : Defines the entry point for the console application.
//

#import "progid:Wscript.Shell" no_namespace \
rename("FreeSpace", "WshFreeSpace") \
rename("ExpandEnvironmentStrings", "WshExpandEnvironmentStrings") \
rename("AddPrinterConnection", "WshAddPrinterConnection") \
rename("SetDefaultPrinter", "WshSetDefaultPrinter") \
rename("DeleteFile", "WshDeleteFile") \
rename("MoveFile", "WshMoveFile") \
rename("CopyFile", "WshCopyFile") 


int main()
{
    return 0;
}

In the next part we shall actually see how to do some interesting things with our new found powers!

ASIDE: The WScript documentation seems to be intended for script users who generally do not have to care for things like interfaces, co-classes etc (which is a good thing sometimes). But it's difficult to work with WScript in C++ if you do not know the interface, co-class names etc. But there is an easy way out of this quandary just compile the above source code and look in the output directory for a file called wshom.tlh this file contains the declarations of all the interfaces, co-classes etc within the component)

Saturday 24 October 2009

Booz: A new interactive shell on the block


Hey guys I have started a new boo based command shell project on codeplex. Its called booz :) here's a link to it http://booz.codeplex.com/wikipage

Please check it out and give me some feedback. Its pre-alpha at this stage but I plan to refine it until it becomes the shell of choice for boo aficionados all over the world :)

I plan to take it in a direction different from booish (the std. boo interpreter/REPL) and make it more appealing to those who wish to use boo as replacement for other regular shells (like cmd.exe for example). Its not just going to be an interpreter/REPL for regular boo but will be augmented with more shell like features.
Regards,
Sandeep Datta

Thursday 22 October 2009

NTFS streams in .net

NTFS streams cannot be opened directly with regular .Net file IO classes, they throw a "System.NotSupportedException: The given path's format is not supported" exception. Here is a C# code sample demonstrating a work-around to the "cannot open NTFS streams in .net (using regular classes)" problem...
using System;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;

namespace NTFSStreams
{
    class Program
    {
        [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
        static extern SafeFileHandle CreateFile(
        string fileName,
        [MarshalAs(UnmanagedType.U4)] FileAccess fileAccess,
        [MarshalAs(UnmanagedType.U4)] FileShare fileShare,
        IntPtr securityAttributes,
        [MarshalAs(UnmanagedType.U4)] FileMode creationDisposition,
        [MarshalAs(UnmanagedType.U4)] FileAttributes flags,
        IntPtr template);



        static void Main(string[] args)
        {

            //Note the special file name construct used to specify the NTFS stream
            const string fileName = "test.txt:stream1";

            //Yes we are going to use CreateFile to bypass the awkward restrictions
            //imposed by the .net file IO classes
            SafeFileHandle handle = CreateFile(fileName
            , FileAccess.ReadWrite
            , FileShare.Read
            , IntPtr.Zero
            , FileMode.OpenOrCreate
            , FileAttributes.Normal
            , IntPtr.Zero);

            if (handle.IsInvalid)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            //Open a .net stream for writing.
            //We will close it here before reading for demonstration purposes
            using (FileStream stream = new FileStream(handle, FileAccess.ReadWrite))
            {
                using (StreamWriter writer = new StreamWriter(stream))
                {
                    writer.Write("Hello world!");
                }
            }


            //Note: The file handle returned by the call to CreateFile is closed 
            //by the SafeFileHandle wrapper around it hence we need to reopen the
            //file here.
            //Alternatively you could have reset the file stream position and 
            //opened a StreamReader on the above FileStream itself.
            handle = CreateFile(fileName
            , FileAccess.Read
            , FileShare.Read
            , IntPtr.Zero
            , FileMode.Open
            , FileAttributes.Normal
            , IntPtr.Zero);

            using (FileStream stream = new FileStream(handle, FileAccess.Read))
            {
                using (StreamReader reader = new StreamReader(stream))
                {
                    while (!reader.EndOfStream)
                    {
                        Console.WriteLine(reader.ReadLine());
                    }
                }
            }
        }
    }
}



References

Friday 21 December 2007

My Technorati claim

Technorati Profile