Key Mapper Developer Blog

Koogra sample

Koogra is an open-source MIT Licensed library for reading Excel files: it's latest release  supports Excel 2007 format.

I use it for an app which imports Excel spreadsheets: using the Office interop libraries is awkward and brittle, and it has to work with an existing install of Office which you can't control versioning or upgrading. So, Koogra. I don't need to write to the spreadsheets, just read them.

There are at least three distinct syntaxes in Koogra:

  1. Excel 2007 syntax
  2. Excel 2003 syntax (more accurately, 97-2003)
  3. The interface syntax which links the two together

For example, finding a worksheet by name:

using Net.SourceForge.Koogra;
using Excel2003 = Net.SourceForge.Koogra.Excel; 
using Excel2007 = Net.SourceForge.Koogra.Excel2007; 

// The Excel 2003 syntax for opening a worksheet 
Excel2003.Workbook workbook = new Excel2003.Workbook(fileName); 
Excel2003.Worksheet worksheet = workbook.Sheets.GetByName(sheetName); 

// The Excel 2007 syntax 
Excel2007.Workbook workbook = new Excel2007.Workbook(fileName); 
Excel2007.Worksheet worksheet = workbook.GetWorksheetByName(sheetName);

// The interface syntax 
IWorkbook workbook; 
if (Path.GetExtension(fileName).ToLower() == ".xlsx") 
{ 
    workbook = new Excel2007.Workbook(fileName); 
} 
else 
{ 
    workbook = new Excel97.Workbook(fileName);
} IWorksheet sheet = workbook.Worksheets.GetWorksheetByName(sheetName);

The only one I am interested in is the interface syntax, as I have to support both file types, so here is a basic code sample for reading some data from a given sheet in a given file.

namespace JustKeepSwimming.Net.Koogra.Example
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using Net.SourceForge.Koogra;
    using Excel97 = Net.SourceForge.Koogra.Excel;
    using Excel2007 = Net.SourceForge.Koogra.Excel2007;

    public class DataImporter
    {
        public List ImportData(IImportData importData, string fileName)
        {
            // The Koogra Excel library uses 0 based counting
            // unlike some others
            List results = new List();

            IWorkbook workbook;

            if (Path.GetExtension(fileName).ToLower() == ".xlsx")
            {
                workbook = new Excel2007.Workbook(fileName);
            }
            else
            {
                workbook = new Excel97.Workbook(fileName);
            }

            IWorksheets sheets = workbook.Worksheets;
            IWorksheet sheet = null;

            if (string.IsNullOrEmpty(importData.SheetName) == false)
            {
                // GetWorksheetByName returns null if name not found..
                sheet = sheets.GetWorksheetByName(importData.SheetName);
            }

            // .. use the first sheet in the workbook.
            if (sheet == null)
            {
                sheet = sheets.GetWorksheetByIndex(0);
            }

            uint lastRow = sheet.LastRow;
            if (importData.IsLastRowTotal)
            {
                lastRow--;
            }

            for (ushort i = (ushort)(importData.StartRow - 1); i <= lastRow; i++)
            {
                IRow row = sheet.Rows.GetRow(i);
                string name = GetCellValue(row, (uint)importData.NameColumn);
                string amountString = GetCellValue(row, (uint)importData.AmountColumn);

                // Parse as double so if Excel returns 7.0000000E-2
                // for 0.07 it's still recognized.
                double amountDouble;
                if (double.TryParse(amountString, out amountDouble))
                {
                    ImportItem item = new ImportItem(name, amountDouble);
                    results.Add(item);
                }
            }
            return results;
        }

        private string GetCellValue(IRow cells, uint column)
        {
            if (cells != null && cells.GetCell(column) != null)
            {
                object value = cells.GetCell(column).Value;
                if (value != null)
                {
                    return value.ToString().Trim();
                }
            }
            return string.Empty;
        }

        private string GetFormattedValue(IRow cells, uint column)
        {
            if (cells != null && cells.GetCell(column) != null)
            {
                return cells.GetCell(column).GetFormattedValue();
            }
            return string.Empty;
        }
    }
}
User Mappings and Windows 7

Windows 7 (the RC build, 7100) no longer implements per-user key mappings: as Microsoft never documented it anyway, removing it is their perogative. This is unfortunate, as it's back to the days of Windows 2000 where key mappings apply to all users without possibility of exception and require administrative privileges to be created or removed. I can only speculate whether they were dropped because they were unsupported, or because they just wouldn't work with Fast User Switching, or accidentally, or they were in some way incompatible with the Windows 7 code.

 

It's a pain for me as KeyMapper expects Windows 7 to behave like Vista (*) and Windows Server 2008 and to implement scancode mappings in HKEY_CURRENT_USER\Keyboard Layout and so the program sets the mappings, we log off and on again, and Windows 7 then ignores the scancode mappings, and the key remapping fails and I get a very polite support email with 'broken' or 'doesn't work' in the subject line.

 

It's still possible to set mappings, though, in Key Mapper but you have to tell the program to 'show / boot mappings' from the Mappings menu and then authorise the Registry change and then reboot your computer.

 

So it looks like some new behaviour for KeyMapper under Windows 7 will be required. I might maybe just try and get a recent build of Windows 7 just to see if just maybe the RTM code will implement per-user key mappings before I even think about what to do (and how to deal with UAC).

 

(*) Because it's 'later' than Vista: Windows 7 was only a gleam when Key Mapper 1.0 was released.

I needed to calculate working days for a project: easy enough to discount Saturdays and Sundays, but UK Bank Holidays a bit trickier. I looked for existing code but by the time I got to page three of a Google search I realised I was going to have to write my own (ironic, really, as this will probably end out on page 64) so I did it on my own time so I can post it here in the hope of perhaps saving someone else the trouble.

 

Apart from Easter, they're all straightforward enough - first and last Monday in May, last Monday in August, checking what day of the week Christmas Day, Boxing Day and New Year's Day fall on and what the substitute days will be: I got the code to do Easter Sunday from here and from that getting the Good Friday and Easter Monday holidays are trivial.

 

Before the code listing, a couple of comments:

 

  • There is no error handling - i.e. it will break for a year value less than 1 or greater than 9999
  • If one-off bank holidays are declared, the program won't take account of them. If you absolutely must count every bank holiday including one-offs, you're better off storing the values in a database, allowing extras to be added, and querying the database for the holidays (you could use this program to populate the data)
  • I've tested them against the new few years of published Bank Holidays, which is good enough for me right now: this project has to be delivered, and as soon as possible. They may not be perfect for all scenarios. The code may nor be correct.

 

This is the bank holiday code:

 

namespace JustKeepSwimming.Net
{
using System;
using System.Collections.Generic;

public static class BankHolidays
{
public static List<DateTime> GetBankHolidays(int year)
{
return GetBankHolidaysForYear(year);
}

private static List<DateTime> GetBankHolidaysForYear(int year)
{
List<DateTime> holidays = new List<DateTime>
{
NewYearsDay(year),
GoodFriday(year),
EasterMonday(year),
MayDayBankHoliday(year),
SpringBankHoliday(year),
SummerBankHoliday(year),
ChristmasDay(year),
BoxingDay(year)
};

return holidays;
}

private static DateTime ChristmasDay(int year)
{
DateTime start = new DateTime(year, 12, 25);

if (start.DayOfWeek == DayOfWeek.Saturday)
{
return start.AddDays(2);
}

if (start.DayOfWeek == DayOfWeek.Sunday)
{
return start.AddDays(1);
}

return start;
}

private static DateTime BoxingDay(int year)
{
DateTime start = new DateTime(year, 12, 26);

if (start.DayOfWeek == DayOfWeek.Saturday)
{
return start.AddDays(2);
}

if (start.DayOfWeek == DayOfWeek.Sunday)
{
return start.AddDays(2); // The Monday will be the substitute day for Christmas Day
}

if (start.DayOfWeek == DayOfWeek.Monday)
{
return start.AddDays(1); // The Monday will still be the substitute day for Christmas Day
}

return start;
}

private static DateTime NewYearsDay(int year)
{
DateTime start = new DateTime(year, 1, 1);

if (start.DayOfWeek == DayOfWeek.Saturday)
{
return start.AddDays(2);
}

if (start.DayOfWeek == DayOfWeek.Sunday)
{
return start.AddDays(1);
}

return start;
}

private static DateTime GoodFriday(int year)
{
return EasterSunday(year).AddDays(-2);
}

private static DateTime EasterMonday(int year)
{
return EasterSunday(year).AddDays(1);
}

private static DateTime SpringBankHoliday(int year)
{
// Last Monday in May
return LastDayOfTheWeekInMonth(year, DayOfWeek.Monday, 5);
}

private static DateTime SummerBankHoliday(int year)
{
// last Monday in August
return LastDayOfTheWeekInMonth(year, DayOfWeek.Monday, 8);
}

private static DateTime LastDayOfTheWeekInMonth(int year, DayOfWeek day, int month)
{
DateTime start = FirstDayOfTheWeekInMonth(year, day, month);
DateTime lastDayInMonth = new DateTime(year, month, DateTime.DaysInMonth(year, month));

while (start.AddDays(6) < lastDayInMonth)
{
start = start.AddDays(7);
}

return start;
}

private static DateTime FirstDayOfTheWeekInMonth(int year, DayOfWeek day, int month)
{
DateTime firstOfMonth = new DateTime(year, month, 1);
DateTime firstDay = firstOfMonth.AddDays((int) day - (int) firstOfMonth.DayOfWeek);

if (firstDay < firstOfMonth)
{
firstDay = firstDay.AddDays(7);
}

return firstDay;
}


private static DateTime MayDayBankHoliday(int year)
{
// First Monday in May
return FirstDayOfTheWeekInMonth(year, DayOfWeek.Monday, 5);
}

private static DateTime EasterSunday(int year)
{
// Tidied up a little from code at
// http://bloggingabout.net/blogs/jschreuder/archive/2005/06/24/7019.aspx

int g = year % 19;
int c = year / 100;
int h = (c - c / 4 - (8 * c + 13) / 25 + 19 * g + 15) % 30;
int i = h - h / 28 * (1 - h / 28 * (29 / (h + 1)) * ((21 - g) / 11));
int day = i - ((year + year / 4 + i + 2 - c + c / 4) % 7) + 28;

int month = 3;
if (day > 31)
{
month++;
day -= 31;
}

return new DateTime(year, month, day);
}
}
}

 

Given that, the working day calculation seems easy enough, although the implementation will vary depending on specific requirements: Do you start from the start date, or end at the end? Is your count inclusive or exclusive or neither? (i.e. does the starting day and/or ending day or neither count in the calculation?)


For this example implementation, the start date does not count, and it is inclusive of the end date.

As the time of the dates is immaterial - it doesn't affect the result - we discard it, then add a minute and a day to start, and two minutes to end.
This avoids issues with DateTimes not having a time, and calculates correctly for the parameters of this implementation.

 

public static int GetWorkingDays(DateTime start, DateTime end)
{
start = start.Date.AddDays(1).AddMinutes(1);
end = end.Date.AddMinutes(2);

int workingDays = 0;
int currentYear = start.Year;
List<DateTime> holidays = BankHolidays.GetBankHolidays(currentYear);
while (start < end)
{
if (start.DayOfWeek != DayOfWeek.Saturday
&& start.DayOfWeek != DayOfWeek.Sunday
&& holidays.Contains(start.Date) == false)
{
workingDays++;
}
start = start.AddDays(1);
if (start.Year != currentYear)
{
currentYear = start.Year;
holidays = BankHolidays.GetBankHolidays(currentYear);
}
}

return workingDays;
}