Friday, April 23, 2010

Remembering the Forgotten Soldiers of a Vanished Empire

It is that time of year again when Australians and New Zealanders remember their fallen war heroes and celebrate the veterans as part of the ANZAC Day festivities. It is an occasion when the self-sacrifice of the few are mourned and mythologized, and the values they fought for are cheered and cherished by the multitude. It is a day when the national fabric is examined, repaired and renewed in solemn ceremonies held across Australia, New Zealand and Gallipoli, Turkey.

On this occasion, I shall remember another group of heroes, second to none in valour, sacrifice, comaraderie and courage under fire, whose stories remain largely untold, who have been shoved into the shadows of history, who have been disowned by their own compatriots. I am talking about Gurkhas, the forgotten soldiers of a vanished empire.

A fortuitous by-product of the 19th century British imperial adventures in India, Gurkhas shared many commonalities with their ANZAC counterparts. Both Gurkhas and ANZACs, along with colonials such as Sikhs in India, fought in wars in which they had no discernible stakes. Both served masters whose chief interest in them lay in using them as shock troops to further their imperial gains. Both were heaped with soaring rhetorical bouquets but cast away thoughtlessly as soon as they outlived their usefulness. Both were used as political pawns by their respective leaders eager to please an empire where the sun never set.

The crucial difference between the Gurkhas and ANZACs lies in the way they were perceived and treated by their own countrymen. ANZACs were retrospectively elevated to the sacred rank of national heroes and living treasures. They formed the bedrock of their nation's founding myth. In the Australian national narrative, the new European settlers' claim to the Great Southern Land, whose original inhabitants were dispossessed, marginalized or killed after the European arrival in 1788, did not become legitimate until young Australians were mowed down by Ottoman Turkish machine guns on the shores of Gallipoli Peninsula in 1915. Such is the price exacted by the birth of a nation state.

Gurkhas occupied the opposite end of the national mythology spectrum. They were an unpleasant reminder of a humiliating defeat inflicted on Nepal by the British East India Company in the Anglo-Nepalese War (1814 to 1816). In a treaty that followed the war, Nepal was forced to cede one-third of its territory, put up with a British Resident in Kathmandu and permit the British East India Company to recruit Nepalese youth into its private army, effectively becoming a British client state. Such is the price exacted upon a nation that comes second best in a collision with a super power.

Furthermore, the British chose to recruit only from those Nepalese ethnic groups that it classified as the "martial races", which also happened to be the newly conquered peoples of Nepal by the Gorkha Kingdom. Hence, the Gorkha ruling elite had at least two reasons to banish Gurkhas from national consciousness and write them out of history books.

On this ANZAC Day this Sunday, when fresh wreaths are laid down and the Last Post sounded for the fallen ANZAC diggers, some of us whose stories are interleaved with those of the Gurkhas will mourn and pay homage to the memory of these forgotten soldiers of a vanished empire, whose exploits in Gallipoli matched and even outshone those of the ANZAC heroes.

Sunday, April 18, 2010

Miserere mei, Deus, or How Two Wunderkinds Foiled a Papal Conspiracy

The story has all the elements of Da Vinci Code minus the horrible murders: A powerful organization that wants to keep the lid on a sacred work it deems too painful for ordinary mortals to bear. An accomplished music composer and priest who sings in the Papal Chapel in Rome from the age of 9 until his death. A Pontiff who makes smoking tobacco an excommunicable offence, orders the most notorious persecution of a scientist in history and pillages the Partheon. An 18th century child prodigy who creates the first known bootleg copy of a piece of music. A 20th century boy with the voice of an angel who sings it into a global sensation. And the celebrated Old Testament King who starts it all.



Welcome to the enchanting world of Miserere mei, Deus, a late Renaissance work for choirs composed by the Italian priest Gregorio Allegri (1582 - 1652), who was an early candidate for the intriguing phenomenon known as the one hit wonder. Although Allegri composed many other works, he, like Pachelbel of Canon and Gigue in D fame, is today solely remembered for a single work, Miserere me, Deus, which is shrouded in secrecy, legend and mystery thanks in no small part to the Vatican.

Allegri was appointed to the Papal Choir in Rome during the reign of Pope Urban VIII (1568 - 1644), who was a scion of the important Florentine family of the Berberini. A great patron of the arts, the Pope earned future notoriety by coercing Galileo to recant his heliocentric theory of the solar system and looting the metal girders from the Partheon to manufacture canons, an act of unimaginable vandalism that prompted the jibe "What the barbarians did not do, the Berberini did".

In the subsequent years, the only place where Miserere could be heard was in the Sistine Chapel in Rome, where it was played during the Holy Week. Like modern-day pilgrims' visit to Google's headquarters in Mountain View, California, it became fashionable for 18th century artists, socialites and aristocrats on European Grand Tour to attend the performance of Miserere in the Papal Chapel and twitter about it in their diaries and letters back home.

Then, suddenly, in 1770,  Miserere became public property thanks to a precocious 14-year old boy named Mozart.  The visiting child prodigy from the Archbishopric of Salzburg listened to the performance of Miserere once, transcribed everything from memory, went back next day to listen to one more performance in order to make some corrections in his copy, and shared his booty with a visiting English traveler, composer and musicologist, Dr. Charles Burney. Dr. Burney published the score in 1771. The Vatican had no choice but to remove the ban and applaud the genius of history's first and most famous music bootlegger.

Some time in the late 19th century, a copyist's error crept into the score of Miserere, the so-called top C, which became part of the canonized version of the work as performed today. Many connoisseurs believe that this chance error actually served to enhance the beauty of the original score, in itself a contentious notion that has added to the work's enduring mystique.

Before the teenage Mozart blew the Vatican ban on Miserere, only three authorized copies of the score were reported to have been made and distributed outside the Vatican. One of the recipients of this papal favor, the Holy Roman Emperor Leopold I, is reported to have remonstrated with the Pontiff that the copy that he received did not match the score as performed in the Papal Chapel as the score was missing ornamentation. Ever since then, rumors and myths have circulated about secret ornamentation that were passed from performer to performer in the Papal Chapel but missing from many unadorned scores that floated around Europe.

The modern version of Miserere itself is an end product of many revisions and editing as the work crossed both temporal and spatial boundaries after its release from the gilded vaults of the Vatican.

It was another wunderkind, a 12 year old British boy named Roy Goodman who propelled Miserere into popular consciousness with his celebrated performance with the Choir of King's College in the March 1963 recording for Decca label. In a pleasing symmetry spanning a couple of centuries, the Renaissance polyphonic masterpiece set to Psalm 51, which was written by King David in a fit of repentance after his adulterous affair with Bathsheba, was finally introduced into the bazaar of popular culture by another daring youngster.

Roy Goodman, currently one of the world's most successful freelance conductors, was recently interviewed by Margaret Throsby on ABC Classic FM radio. You can listen to the interview here.

Saturday, April 10, 2010

Remembering Girija

Reading through some obits of late Girija Prasad Koirala (1925 - 2010) in the Nepalese media, it seems that in the eye of some commentators, Koirala managed to redeem himself somewhat in the final decade of his life. To begin with, he is praised for standing up to the then Nepalese King Gyanendra, who assumed absolute power in a short-sighted royal coup in 2002 that spelled the end of monarchy in Nepal. Koirala is also credited with persuading parliamentary parties to sign a 12-point agreement with Maoists in Delhi in 2005 that paved the way for the peace process that halted a decade-long armed Maoist insurgency.

The vast majority of the Nepalese people remained hostile to Koirala until the very end, however. Many were unmoved by the news of his death, openly expressing glee and hope that his death would herald a new dawn for Nepal. Others pointed out that even his practical rapproachment with the Maoists was motivated by what many consider to be the most serious blemish on his legacy: nepotism and self-aggrandizement. According to this interpretation, Koirala wanted to resurrect his political fortunes and dynastic ambitions by aligning himself with his erstwhile sworn foes, the Maoists.

Whatever one's opinions about Koirala, he was indisputably one of the most divisive, controversial and dominant political players in the post-1990 Nepal. His early political apprenticeship itself reads like that of a Mafioso enforcer. Living in the shadows of his illustrious older brother BP, who surely was the toughest act to follow in the Nepalese politics, Koirala did the dirty work for his party, the Nepalese Congress. Organizing Biratnanar Jute Mill workers to agitate against the autocratic Rana regime, smuggling weapons across the Nepal-India border to arm anti-Rana forces, hijacking a currency-laden Royal Nepal Airlines plane, printing counterfeit Indian Rupees and eliminating party rivals were all in the day's work for a young Koirala.

In his autobiography, BP, whose literary legacy might yet outshine and outlive his considerable political legacy, famously dismissed his younger brother GP as a party hawaldar or foot soldier, anointing his niece Sailaja as his political heir.

In the wake of the 1990 People's Movement that restored multi-party democracy in Nepal, Koirala emerged as an influential member of the powerful Nepalese Congress triumvirate along with Ganeshman Singh and Krishna Prasad Bhattarai. In the political chaos that followed, Koirala managed to sideline his two key party rivals amidst bitter public feuds, and did his best to undermine a short-lived government of Communist Party of Nepal (United Marxist Leninist). In the end, he surpassed his older brother BP's wildest dreams by becoming Prime Minister for five times on the back of his undoubted skills at mobilizing the party faithful.

As a reporter for The Kathmandu Post, I had occasion to cover many tedious ceremonies and functions in which Koirala cut ribbons and/or starred as the chief guest. One such occasion left an indelible mark on my mind.

The function was held at the Blue Star Hotel. Koirla was not holding any official position at the time - of course, nothing less than the Prime Minister's Office would have suited his ego - even though his party had formed the government. One of the senior ministers in the government, Bal Bahadur Rai, was also invited to the function.

Rai committed the unpardonable sin of arriving at the function after his party overlord Koirala, whose career was far from over despite appearances. As if to prove this point, a sanctimonious Koirala went on to give a very public dressing down to the embarrassed senior minister, scolding him like a mischievous school boy for arriving late at the "people's function". All that Minister Rai, who looked more like a dreamy, self-satisfied Abbot of a Buddhist monastery than a senior Nepali Congress leader and cabinet minister that he was, could do to counter the hypocritical and self-righteous tirade from his boss was to grin sheepishly and fold his hands together in a gesture of feigned humility and remorse.

Tuesday, January 5, 2010

Parsing INI Files With VBA

INI files are still widely used to enable users to configure applications even though their use is deprecated in the Windows world. The recommended method is to write user configuration data to the Windows registry.

From VBA Word, the INI files (and Windows registry) can be accessed and modified through the VBA Object library function System.PrivateProfileString(). In VBA Excel, this library call is not available. To read INI files, one option is to employ the Windows API function GetPrivateProfileString Lib "kernel32". The other option is to write your own INI parser, which is what I did the other day.

Apart from the normal comments (comments start with # in this version of INI), the script also tolerates end-of-line comments.

The script also enforces unique section names by generating an error if a duplicate section name is detected. This is quite important as the script stores the INI data as key-value pairs, and the way it ensures unique key names is by prefixing section names to key names, with a dot separating the two.

The script also gives users the option of loading the generated data file to Teradata using the FastLoad utility.

Sample ini file:

# Author: Ram Limbu
# Date: 2009/11/28
#
#
#

[FILE_SETTINGS] # start of file settings

DestDir=C:\Documents and Settings\Ram_2\My Documents\Excel Projects\Config # don't end this line with slash
Delimiter=,
DestFileName=ErrorFiles.csv

[DATABASE_SETTINGS] # start of database settings

InsertData=Yes # Acceptable values: Yes or No
DbName=IPSHARE
TblName=RL_PP_Hist
DbUsername=cncra/RLIMBU
DbPw=secret



Option Explicit


Const MsgTitle As String = "Wooden Horse Pty Ltd"

Sub Batch_Err_Parser()

'//define constants
Const OldDelim As String = "|"
Const NewDefDelim As String = ","
Const Colon As String = ":"
Const BackSlash As String = "\"

'//define vars
Dim LastRow As Long
Dim Rec As String
Dim TimeStamp As String
Dim Msn As String
Dim RejCode As String
Dim RejMsg As String
Dim FileName As String
Dim DestDir As String
Dim DestFile As String
Dim NewDelim As String
Dim Identifier As String
Dim OrderNotes As String
Dim Status As String
Dim t1 As Double
Dim t2 As Double
Dim Rng As Range
Dim C As Range
Dim Arr As Variant
Dim ColonPos1 As Integer
Dim ColonPos2 As Integer
Dim TotPasses As Integer
Dim TotFails As Integer
Dim RecCtr As Long
Dim DatExists As Boolean
Dim fs As Object
Dim ts As Object
Dim ws As Worksheet
Dim INI_COL As New Collection


On Error GoTo Err_Rtn:

t1 = Timer

'// call initialization rtn
InitRtn

'//ensure that it is a batch error rpt
If Mid(Cells(1, 1), 3, 8) <> "RPT_OPOM" Then
If Not MsgBox("Is this Marketing Batch Error report?", vbYesNo + vbQuestion, MsgTitle) Then
Exit Sub
End If
End If

Set ws = ActiveSheet

'// find the last row
LastRow = ws.Cells(65536, 1).End(xlUp).Row

'// if there is no data, exit
If LastRow = 1 Then
MsgBox "Goodbye!", vbExclamation, MsgTitle
Exit Sub
End If

'// get ini data
If Rtn_Config_Col(INI_COL) <> 0 Then '// 0 = successful func call
Exit Sub
End If

'// read new delim from ini settings
NewDelim = INI_COL.Item("[FILE_SETTINGS].Delimiter")
If Trim(NewDelim) = "" Then
NewDelim = NewDefDelim
End If

'// read identifier
Identifier = INI_COL.Item("[FILE_SETTINGS].Identifier")

'// if headers are turned on, write them to data file
If UCase(INI_COL.Item("[FILE_SETTINGS].Headers")) = "ON" Then
Rec = "service_no" & NewDelim & "status" & NewDelim & "time_stamp" & NewDelim & _
"rej_code" & NewDelim & "rej_descript" & NewDelim & "identifier" & vbCrLf
End If

'// create the data range
Set Rng = ws.Cells(1, 1).Resize(LastRow)

'// loop the cells and parse the records
Application.DisplayStatusBar = True

For Each C In Rng
Status = Trim(Left(C, 4))
If Status = "PASS" Or Status = "FAIL" Then
RecCtr = RecCtr + 1
Application.StatusBar = "Processing record: " & RecCtr
If Status = "PASS" Then
TotPasses = TotPasses + 1
Else
TotFails = TotFails + 1
End If
If Status = "FAIL" Or (Status = "PASS" And UCase(INI_COL.Item("[FILE_SETTINGS].FailOnly")) <> "YES") Then
Arr = Split(C, OldDelim)
Msn = Right(Arr(4), 10)
ColonPos1 = InStr(1, Arr(6), Colon, vbTextCompare) ' find 1st position of colon
ColonPos2 = InStr(ColonPos1 + 1, Arr(6), Colon, vbTextCompare) ' find 2nd position of colon
RejCode = Trim(Mid(Arr(6), ColonPos1 + 1, ColonPos2 - ColonPos1 - 1)) ' extract rej code
RejMsg = Trim(Mid(Arr(6), ColonPos2 + 1, Len(Arr(6)))) ' extract rejection description
OrderNotes = Trim(Arr(7)) 'extract order notes
OrderNotes = Right(OrderNotes, Len(OrderNotes) - InStr(OrderNotes, Colon)) 'extract only aft colon
RejMsg = RejMsg & " " & OrderNotes
RejMsg = Left(RejMsg, 298) ' ensure that rejmsg is not more than 298 characters as db vartext field is 300 only
Rec = Rec & Msn & NewDelim & Arr(0) & NewDelim & TimeStamp & NewDelim & _
RejCode & NewDelim & RejMsg & NewDelim & Identifier & vbCrLf
DatExists = True
End If
ElseIf Status = "DATE" Then
TimeStamp = Right(C, 19)
ElseIf UCase(Left(C, 15)) = "INPUT FILE NAME" Then
FileName = Trim(Right(C, Len(C) - 16))
End If
DoEvents
Next C '// end of main loop

'// if error records exist, write them to a file
If DatExists Then
Application.StatusBar = "Writing to text file ..."
'// read dest folder and file details from ini setting collection
DestDir = INI_COL.Item("[FILE_SETTINGS].DestDir")

'// if there is no slash at the end of destination folder, put one there
If Right$(DestDir, 1) <> BackSlash Then
DestDir = DestDir & BackSlash
End If

DestFile = INI_COL.Item("[FILE_SETTINGS].DestFile")
If UCase(Trim(DestFile)) = "DEFAULT" Or Trim(DestFile) = "" Then '// if no filename has been supplied, name it after error file
If FileName <> "" Then '// ensure error report filename exists
DestFile = FileName
Else
MsgBox "Error: No valid destination file could be found. Exiting ...", vbOKOnly + vbInformation, MsgTitle
Exit Sub
End If
End If

Set fs = CreateObject("Scripting.FileSystemObject")
If fs.FolderExists(DestDir) Then
DestFile = DestDir & DestFile 'prepend dest dir
Set ts = fs.CreateTextFile(DestFile)
ts.Write Rec
ts.Close
Else
MsgBox DestDir & " doesn't exist or not accessible!", vbOKOnly + vbCritical, MsgTitle
Exit Sub
End If

'do clean up
Set fs = Nothing
Set ts = Nothing
Set ws = Nothing

'if the user has elected to write records to db, fastload them
If UCase(INI_COL.Item("[DATABASE_SETTINGS].InsertData")) = "YES" Then
'//ensure all other database setting values are provided
With INI_COL
If .Item("[DATABASE_SETTINGS].DbName") <> "" And .Item("[DATABASE_SETTINGS].TblName") <> "" & _
.Item("[DATABASE_SETTINGS].DbUsername") <> "" And .Item("[DATABASE_SETTINGS].DbPw") <> "" Then
DbRtn Db:=.Item("[DATABASE_SETTINGS].DbName"), Tbl:=.Item("[DATABASE_SETTINGS].TblName"), _
UsrNm:=.Item("[DATABASE_SETTINGS].DbUsername"), Pw:=.Item("[DATABASE_SETTINGS].DbPw"), _
InputFile:=DestFile, TargetDir:=DestDir, Start:=IIf(UCase(.Item("[FILE_SETTINGS].Headers")) = "ON", 2, 1), _
Delim:=NewDelim
Else
MsgBox "Since you didn't provide all required database settings, records were not " & vbCrLf _
& "written to database!", vbInformation + vbOKOnly, MsgTitle
End If
End With

End If

t2 = Timer
Application.StatusBar = "Done!"
MsgBox "Finito! Time taken = " & Format(t2 - t1, "##0.000") & " seconds" & vbCrLf _
& "Total Passes = " & TotPasses & vbCrLf _
& "Total Fails = " & TotFails & vbCrLf _
& "Total Records = " & TotPasses + TotFails, vbInformation + vbOKOnly, MsgTitle

End If
Application.StatusBar = ""
Exit Sub
Err_Rtn:
Application.StatusBar = ""
MsgBox Err.Description, vbInformation + vbOKOnly, MsgTitle
End Sub

Private Function Rtn_Config_Col(ByRef INI_COL As Collection) As Integer
Dim INI_File As String
Dim Rec As String
Dim SecName As String
Dim Kee As String
Dim val As String
Dim SubStrPos As Integer
Dim fs As Object
Dim f As Object
Dim flds As Object
Dim SecCol As New Collection


'// define some constants to make script more userfrieldly
Const READ_ONLY As Integer = 1
Const CommStr As String = "#"
Const Eq As String = "="
Const SecStart As String = "["
Const SecEnd As String = "]"
Const MinValLen As String = 3
Const Blank As String = ""

Set fs = CreateObject("WScript.Shell")
Set flds = fs.SpecialFolders
INI_File = flds("mydocuments") & "\Batch_Errors\Batch_Errors.ini"
Set fs = CreateObject("Scripting.FileSystemObject")

'//ensure ini file exists
If Not fs.FileExists(INI_File) Then
MsgBox "My Documements\Batch_Errors folder " & vbCrLf & _
"or INI file does not exist! Exiting ...", vbCritical + vbOKOnly, MsgTitle
Rtn_Config_Col = -1
End If

Set f = fs.OpenTextFile(INI_File, READ_ONLY)

'//read the ini file

Do Until f.AtEndOfStream

Rec = Trim(f.ReadLine)

'// ignore comments and blank lines
If Left(Rec, 1) <> CommStr And Rec <> Blank Then
'// check to see if there are comments at end of key-value pair
'// if so, take them out of current record
SubStrPos = InStr(2, Rec, CommStr)

If SubStrPos > 0 Then '// end of line comments exists
Rec = Trim(Left(Rec, SubStrPos - 1)) '// eliminate comments
End If

'//check for section name
If Left(Rec, 1) = SecStart Then
If Right(Rec, 1) = SecEnd And Len(Rec) >= MinValLen Then '// ensure it is valid section name
'ensure that there are no duplicate section names
SecName = Rec
On Error Resume Next
SecCol.Add SecName, SecName
If Err <> 0 Then
MsgBox "Error: The INI file has duplicate section names! Exiting ...", vbCritical + vbOKOnly, MsgTitle
Rtn_Config_Col = -1
End If
End If
Else
'// check if it is key-value pair
SubStrPos = InStr(2, Rec, Eq)
If SubStrPos > 0 And Len(Rec) >= MinValLen Then
'// insert key-value pair into collection
'// prepend section name to key name to make it unique
Kee = Left(Rec, SubStrPos - 1)
val = Trim(CleanData(Right(Rec, Len(Rec) - SubStrPos)))
Kee = Trim(CleanData(SecName & "." & Kee))
INI_COL.Add val, CStr(Kee)
End If
End If
End If
Loop
f.Close
Set fs = Nothing
Set flds = Nothing
Rtn_Config_Col = 0
End Function

Private Function CleanData(Data As String) As String
Dim i As Integer
For i = 0 To 31
While InStr(1, Data, Chr(i)) > 0
Data = Replace(Data, Chr(i), " ")
Wend
Next i

For i = 127 To 255
While InStr(1, Data, Chr(i)) > 0
Data = Replace(Data, Chr(i), " ")
Wend
Next i
CleanData = Data
End Function

Private Sub InitRtn()
'// in this routine, ensure that user has
'// Batch_Errors.ini file within Batch_Errors in My Documents folder. If not, create one
'// and inform user
Dim fso As Object
Dim fws As Object
Dim sfld As Object
Dim Rec As String
Dim txt_file_o As Object

Set fso = CreateObject("Scripting.FileSystemObject")
Set fws = CreateObject("WScript.Shell")
Set sfld = fws.SpecialFolders
If Not fso.FolderExists(sfld("mydocuments") & "\Batch_Errors") Then
'// create Batch_Errors folder
Application.StatusBar = "Creating " & sfld("mydocuments") & "\Batch_Errors"
fso.CreateFolder (sfld("mydocuments") & "\Batch_Errors")
Application.StatusBar = "Creating .ini file ..."

'// create ini file
Rec = "# This .ini file was created by batch error parser on " & Format(Now, "YYYY-MM-DD HH:MM:SS") & vbCrLf & "#" & vbCrLf
Rec = Rec & "# Comments start with the hash symbol (#), just like this line" & vbCrLf & "#" & vbCrLf & "#" & vbCrLf
Rec = Rec & "# start of file settings" & vbCrLf & vbCrLf
Rec = Rec & "[FILE_SETTINGS]" & vbCrLf & vbCrLf
Rec = Rec & "DestDir=" & sfld("mydocuments") & "\Batch_Errors" & vbCrLf
Rec = Rec & "Delimiter=, # leaving it blank will choose comma as default delimiter" & vbCrLf
Rec = Rec & "DestFile=default # the destination file name defaults to error report name" & vbCrLf
Rec = Rec & "Headers=On # On or Off" & vbCrLf
Rec = Rec & "Identifier= # write something descriptive to identify service numbers or leave blank" & vbCrLf
Rec = Rec & "FailOnly=No # Yes or No. If Yes, only services that returned rejections will be extracted" & vbCrLf & vbCrLf
Rec = Rec & "# start of database settings" & vbCrLf & vbCrLf
Rec = Rec & "# If you want the error report data to insert into a table, then provide appropriate values " & vbCrLf
Rec = Rec & "# for the following database settings, starting with 'InsertData'." & vbCrLf
Rec = Rec & "# *** Please, note: you need to create the required table as follows" & vbCrLf
Rec = Rec & "# before attempting to load data into it via this utility!" & vbCrLf
Rec = Rec & "#" & String(50, "-") & vbCrLf
Rec = Rec & "# CREATE SET TABLE DB_FOO.TBL_BAR (" & vbCrLf
Rec = Rec & "# service_no VARCHAR(20)" & vbCrLf
Rec = Rec & "# ,status VARCHAR(10)" & vbCrLf
Rec = Rec & "# ,tm_stamp VARCHAR(30)" & vbCrLf
Rec = Rec & "# ,rej_code VARCHAR(10)" & vbCrLf
Rec = Rec & "# ,rej_descript VARCHAR(300)" & vbCrLf
Rec = Rec & "# ,identifier VARCHAR(50)" & vbCrLf
Rec = Rec & "# ,open_dt DATE" & vbCrLf
Rec = Rec & "# ,close_dt VARCHAR(30)" & vbCrLf
Rec = Rec & "# ) PRIMARY INDEX (service_no);" & vbCrLf
Rec = Rec & "#" & String(50, "-") & vbCrLf & vbCrLf
Rec = Rec & "# Since the data get FastLoaded, you need to have FastLoad utility installed on your machine." & vbCrLf & vbCrLf
Rec = Rec & "[DATABASE_SETTINGS]" & vbCrLf & vbCrLf
Rec = Rec & "InsertData=No # Yes or No. To insert data into database, write 'Yes'" & vbCrLf
Rec = Rec & "DbName=xx_my_db # replace 'xx_my_db' with your database name, i.e. IPSHARE" & vbCrLf
Rec = Rec & "TblName=xx_my_tbl # replace 'xx_my_tbl' with your table name" & vbCrLf
Rec = Rec & "DbUsername=cncra/jdoe # replace 'cncra/jdoe' with your data source name/username" & vbCrLf
Rec = Rec & "DbPw=secret # replace 'secret' with your password"

Set txt_file_o = fso.CreateTextFile(sfld("mydocuments") & "\Batch_Errors\Batch_Errors.ini")
txt_file_o.Write Rec
txt_file_o.Close
Set txt_file_o = Nothing
Application.StatusBar = ""
End If

End Sub

Sub DbRtn(Db As String, Tbl As String, UsrNm As String, Pw As String, InputFile As String, TargetDir As String, Start As Integer, Delim As String)
'// this routine fastloads error records into named tables

Dim flScript As String
Dim fso As Object
Dim fo As Object
Dim ScriptInput As String
Dim ScriptOutput As String
Dim ShStatus As Double
Dim BatOut As String


'// start creating fastload script

flScript = "/* " & String(84, "+") & " */" & vbCrLf
flScript = flScript & "/* FASTLOAD SCRIPT CREATED BY BATCH ERROR PARSER */" & vbCrLf
flScript = flScript & "/* ON " & Format(Now, "YYYY-MM-DD hh:mm:ss") & " TO FASTLOAD BATCH ERROR DATA */" & vbCrLf
flScript = flScript & "/* " & String(84, "+") & " */" & vbCrLf
flScript = flScript & vbCrLf & "/* Setup FastLoad parameters */" & vbCrLf & vbCrLf
flScript = flScript & "SESSIONS 50;" & vbCrLf
flScript = flScript & "ERRLIMIT 50;" & vbCrLf
flScript = flScript & "LOGON " & UsrNm & ", " & Pw & ";" & vbCrLf
flScript = flScript & "SHOW VERSIONS;" & vbCrLf
flScript = flScript & "RECORD " & Start & ";" & vbCrLf
flScript = flScript & "SET RECORD VARTEXT """ & Delim & """ DISPLAY_ERRORS NOSTOP;" & vbCrLf & vbCrLf
flScript = flScript & "/* Define text file layout and input file */" & vbCrLf & vbCrLf
flScript = flScript & "DEFINE service_number (VARCHAR(20))" & vbCrLf
flScript = flScript & " ,status (VARCHAR(10))" & vbCrLf
flScript = flScript & " ,tm_stamp (VARCHAR(30))" & vbCrLf
flScript = flScript & " ,rej_code (VARCHAR(10))" & vbCrLf
flScript = flScript & " ,rej_descript (VARCHAR(300))" & vbCrLf
flScript = flScript & " ,identifier (VARCHAR(50)) " & vbCrLf
flScript = flScript & "FILE=" & InputFile & ";" & vbCrLf & vbCrLf
flScript = flScript & "SHOW;" & vbCrLf & vbCrLf
flScript = flScript & "BEGIN LOADING " & Db & "." & Tbl & vbCrLf
flScript = flScript & " ERRORFILES " & Db & ".Batch_Err1, " & Db & ".Batch_Err2" & vbCrLf
flScript = flScript & " CHECKPOINT 500;" & vbCrLf & vbCrLf
flScript = flScript & "INSERT INTO " & Db & "." & Tbl & " VALUES" & vbCrLf
flScript = flScript & " ( :service_number " & vbCrLf
flScript = flScript & " ,:status " & vbCrLf
flScript = flScript & " ,:tm_stamp " & vbCrLf
flScript = flScript & " ,:rej_code " & vbCrLf
flScript = flScript & " ,:rej_descript " & vbCrLf
flScript = flScript & " ,:identifier " & vbCrLf
flScript = flScript & " ,CURRENT_DATE " & vbCrLf
flScript = flScript & " ,'2899-12-31'); " & vbCrLf & vbCrLf
flScript = flScript & "END LOADING;" & vbCrLf
flScript = flScript & "LOGOFF;"

Set fso = CreateObject("Scripting.FileSystemObject")
ScriptInput = TargetDir & "FastLoadThis" & ".src"
ScriptOutput = TargetDir & "FastLoad_" & Format(Date, "YYYY_MM_DD") & ".log"
BatOut = TargetDir & "Batch_Errors.bat"

On Error Resume Next
Kill BatOut
Kill ScriptInput
On Error GoTo 0

Set fo = fso.CreateTextFile(ScriptInput)
fo.Write flScript
fo.Close

'//create bat file
flScript = "FastLoad < """ & ScriptInput & """ > """ & ScriptOutput & """ 2>&1"
Set fo = fso.CreateTextFile(BatOut)
fo.Write flScript
fo.Close

'do clean-up
Set fso = Nothing
Set fo = Nothing

'//start fastload
Call Shell(BatOut)

End Sub

Monday, September 14, 2009

Deleting Rows in Excel with VBA

Deleting and shifting Rows up in VBA is a straightforward task that nevertheless hides a few traps. Say, any Row that has a numeric value in Column 'A' needs to be deleted and the Rows below shifted up. A VBA solution such as the one that follows does not work.

Option Explicit 

Sub DeleteRows()
Dim LastRow, Ctr As Long  

'// find the last Row with data
    LastRow = Cells(65536, 1).End(xlUp).Row

'// loop through the Rows, deleteing Rows that have a
    '// numberic value in Column 'A' and shifting Rows up   

    For Ctr = 1 To LastRow
        If IsNumeric(Cells(Ctr, 1)) Then
            Cells(Ctr, 1).EntireRow.Delete Shift:=xlShiftUp
        End If
    Next Ctr
End Sub 

The problem is that if two or more consecutive Rows have numeric values in Column 'A', not all the Rows that need to be deleted get deleted. Why? Say that Rows 3 and 4 have numeric values in Column 'A'. When Row 3 is deleted, Row 4 moves up to take its place, in effect becoming Row 3. Meanwhile, the loop counter gets incremented in the next pass from 3 to 4, with the code scanning the value in Cell A4 but inadvertently skipping the value in Cell A3. 

An elegant solution to this problem is to start looping at the last Row with data in the Worksheet and move up one Row at a time, deleting and shifting Rows up when the required condition is satisfied. Here is the solution: 
Option Explicit 

Sub DeleteRows()
    Dim LastRow, Ctr As Long   

    '// find the last row with data
    LastRow = Cells(65536, 1).End(xlUp).Row   

    '// loop through the Rows, deleteing Rows that have a
    '// numberic value in Column 'A' and shifting Rows up    

    For Ctr = LastRow To 1 Step -1
        If IsNumeric(Cells(Ctr, 1)) Then
            Cells(Ctr, 1).EntireRow.Delete Shift:=xlShiftUp
        End If
    Next Ctr 
End Sub

Saturday, September 12, 2009

The Joy of Running

My tongue-in-cheek remark to a friend after reading Christopher McDougall's Born To Run was this: "Marathons are for wimps". Now, I have yet to run a marathon and I have no illusions about the dedication, hard work, toughness and tenacity required to complete a marathon but after reading about 'ultra freaks' running up and down the 10,000+ feet high Colorado mountains for 100 miles, or plodding 135 miles across the floor of the Death Valley in California in 135 degrees heat, one can be forgiven for thinking that marathons are a stroll in the park.

Born To Run opens with an all too familiar story. A beefy former war correspondent who is built like someone that nature intended 'to take a bullet for the President' rather than pound down the pavement, Mcdougall is doing the rounds of sports shrinks to treat his dodgy feet. In the winter of 2003, he is in Mexico chasing a missing pop star for The New York Times Magazine when he stumbles across a picture of a Jesus like figure running down a rockslide. Welcome to the secret world of Tarahumara Indians, the greatest ultra runners the world has never heard about.

Living like invisible ghosts in the shadowy recesses of the Copper Canyons of Mexico, the peaceful Tarahumara tribes have for centuries outrun their Indian and European tormentors alike just to survive. What is the secret behind their superhuman prowess that enables them to run for hundreds of miles without rest? This is the question that persuades Mcdougall to abandon his celebrity hunt and start another type of hunt, the hunt for a mythical gringo named Caballo Blanco, the 'white horse' who lives with the Tarahumara and runs like one.

Before the book finishes, there is a gripping duel over 100 miles of punishing Colorado trails between a Community College teacher named Ann Tracy and top Tarahumara runners who have been coaxed to participate in the Leadville 100 ultramarathon with promises of corn bags for their villages. I felt the description of this race alone gave all the bang for my bucks.

The book climaxes with a 50 mile foot race in the treacherous Tarahumara territory between the cream of the Tarahumara tribe and the superstars of the North American - which is to say, the world's - ultramarathon scene.

And the secret to the Tarahumara success? In a sentence or three, it is this: All humans are hardwired to run. As kids, all of us run with joyful abandon. As we grow up, we lose this sense of playfulness and sheer joy in running. The Tarahumara keep this playful spirit alive into adulthood, and they have over the millenia woven the art of running into rituals that govern their daily lives. Also, they do not buy expensive, gel-filled Nike running shoes that seem to do more harm than good.

Tuesday, September 8, 2009

Shantaram: Crime and Redemption

If one has to summarise in three words Gregory David Roberts' 2004 debut novel Shantaram, then it has to be this: "Dostoevsky meets Bollywood". 

Or, that is what I thought after yet another scene in the novel in which Bombay's (when the story unfolds, the bustling Indian island city is about another couple of decades away from being re-named Mumbai in response to a Hindu nationalist popular groundswell) chic set banter and indulge themselves in one of the city's fashionable tourist haunts by the sea. 

And what a chic set it is. Roberts, who tells the story in first person, drinks, smokes, eats and philosophises with local Indian journalists, film producers, mafia enforcers, Iranian army deserters, washed-up Palestinian fighters, Pakistani spies, European outcasts, and an Afghan mafia boss who claims to have found the rational basis of morality.   

And just like Bollywood movies that the author claims to love, the novel packs a lot of action, and enough surprises to satisfy even the most demanding fan of Bollywood suspense thrillers. However, the book is much more than a pulp version of Bollywood. 

At the heart of the narrative is that eternal quest for meaning, love and redemption that inform all great works of art. Roberts, who escaped from a maximum security jail in Victoria while serving a sentence for armed robbery, finds all of these and more in the most unlikely of places and persons. 

Towards the very end of the 933 pages long book, Roberts declares: "The truth is that, no matter what kind of game you find yourself in, no matter how good or bad the luck, you can change your life completely with a single thought or a single act of love". 

One can only say "Amen!" to that and recommend the book as a must read.