Tuesday, March 31, 2009

Saving latest versions of procedures from a Metastorm BPM database

Save Metastorm Procedures

One thing that comes up quite often is how to get all the latest procedures from a Metastorm database. The FreeFlow Administrator makes this easy. Simply load it up, connect to the database and use the ‘Save procedures…’ action. Select the folder to save the procedures to and everything will be saved there, included all libraries.

Like everything else in the FreeFlow Administrator, this can all be achieved programmatically, with something like the following.

      Server server = new Server();
      server.Connect("sa", "a", "Metastorm75");
      server.RetrieveLatestProcedures("c:\\temp");

Why would you want to do this? Perhaps you regularly want to get all the latest versions of the procedures in your Metastorm database before you head out of the office and want a script/console app that does this for you.

Finally, you can also get the latest versions of a specific procedure, using something like this

      server.Procedures["Flight"].Versions.LatestVersion.SaveToFile("c:\\temp\\flight.xep");

Saturday, March 28, 2009

XSLT to generate an HTML listing of your iTunes library

The data for the music in an iTunes library is stored in an XML file which means that it should be simple to produce an XSLT file to generate an HTML document listing all the albums in your iTunes library. Well, it would be, but the XML format used by iTunes is, how can I put this, idiosyncratic. Other people may use more fragrant language to describe it…

Anyway I found this very useful article describing how to create an XSLT file to do almost what I wanted, but I wasn’t interested in grouping by genre, since the genre data is often not very accurate or helpful. I’m also a little anal about my music collection and wanted it sorted by artist name, rather than album name. This part was slightly tricky because the data may not be complete. The album artist field is often not entered and the artist field may not be the album artist, even when the album is not a compilation. And finally the compilation flag may be set when you don’t expect it to be or conversely not set when you expect it to be. e.g. a greatest hits album may be flagged as a compilation. This is arguably correct, but I wanted these kind of albums to be grouped with the artist. So quite a few changes were required which is why I’m posting this, it’s not just me trying to get some reflected glory (and hence me linking to the original article repeatedly)

Anyway, the basic idea is the same as the original article. First put an XML file in the same directory as your iTunes library, as follows

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="albumList.xsl" type="text/xsl"?>
<wrapper>
  <incl file="iTunes Music Library.xml"/>
</wrapper>

Next is the XSLT, in a file called albumList.xsl in the same directory, which looks like this.

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="html" encoding="UTF-8" indent="yes"/>

  <!-- match the wrapper and apply templates to the <incl> xml file -->
  <xsl:template match="/wrapper">
    <xsl:apply-templates select="document(incl/@file)/plist/dict/dict"/>
  </xsl:template>
  
  <xsl:key name="songsByAlbum" match="dict" use="string[preceding-sibling::key[1]='Album']"/>

  <xsl:template match="dict">
    <html>
      <head>
        <title>iTunes Album Listing</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <style>
          body
          {
            font-family:Arial;
          }
        </style>
      </head>
      <body>
        <table>
          <thead>
            <tr>
              <td><b>Artist</b></td>
              <td><b>Album</b></td>
            </tr>
          </thead>
          <xsl:variable name="song" select="/plist/dict/dict/dict"/>

          <!-- output the albums that aren't compilations -->
          <xsl:for-each select="$song[generate-id(.)=
        generate-id(key('songsByAlbum',string[preceding-sibling::key[1]='Album'])[1])]">
            <xsl:sort select="concat(string[preceding-sibling::key[1]='Album Artist'], string[preceding-sibling::key[1]='Artist'])"/>

            <xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
          [not(true[preceding-sibling::key[1]='Disabled'])]
          [not(true[preceding-sibling::key[1]='Compilation'])]            
          [1]">
              <xsl:call-template name="outputAlbum" />
            </xsl:for-each>
          </xsl:for-each>

          <!-- output each compilation -->
          <xsl:for-each select="$song[generate-id(.)=
        generate-id(key('songsByAlbum',string[preceding-sibling::key[1]='Album'])[1])]">
            <xsl:sort select="string[preceding-sibling::key[1]='Album']"/>

            <xsl:for-each select="key('songsByAlbum',string[preceding-sibling::key[1]='Album'])
          [not(true[preceding-sibling::key[1]='Disabled'])]
          [true[preceding-sibling::key[1]='Compilation']]
          [not(string[preceding-sibling::key[1]='Album Artist'])]
          [1]">
              <xsl:call-template name="outputAlbum" />
            </xsl:for-each>
          </xsl:for-each>
        </table>
      </body>
    </html>
  </xsl:template>

  <xsl:template name="outputAlbum">
    <tr valign='top'>
      <!-- the artist: -->
      <td>
        <xsl:choose>
          <xsl:when test="string[preceding-sibling::key[1]='Album Artist']">
            <xsl:value-of select="string[preceding-sibling::key[1]='Album Artist']"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:choose>
              <xsl:when test="true[preceding-sibling::key[1]='Compilation']">
                <i>Compilation</i>
              </xsl:when>
              <xsl:otherwise>
                <xsl:value-of select="string[preceding-sibling::key[1]='Artist']"/>
              </xsl:otherwise>
            </xsl:choose>
          </xsl:otherwise>
        </xsl:choose>
      </td>
      <!-- the album name: -->
      <td>
        <xsl:value-of select="string[preceding-sibling::key[1]='Album']"/>
      </td>
    </tr>
  </xsl:template>
  
</xsl:stylesheet>

Now open the XML file in your favoured browser (as long as your favoured browser is IE or FireFox) and the HTML should be generated. It can be slow, it might be the XSLT can be optimised somewhat but I’m not hugely experienced in optimising XSLT so haven’t investigated further. And of course the XSLT can’t cope with all rubbish data in your iTunes library, garbage in, garbage out.

One other improvement would be to improve the sorting so that artists such as The Fall came under F, rather than T, but I have no idea how to achieve that.

Tuesday, March 24, 2009

Saving all folder attachments from a Metastorm database

A question came up on the Metastorm BPM forums about saving all folder attachments from a Metastorm database. Folder data is saved in a particularly strange format so this wouldn’t be very straightforward but with the FreeFlow .NET library things are much simpler. I thought I’d post it here just to show how easy it is. In fact I may start posting some more simple FreeFlow examples here, rather than posting them as downloads on the FreeFlow site, because it’s much easier than fiddling with ASP.NET pages.

It’s a console application that should work with .NET 2 upwards.

using System;

using FreeFlow.Administration;

namespace GetAllAttachments
{
  class Program
  {
    static void Main(string[] args)
    {
      Server server = new Server();
      server.Connect("sa", "a", "Metastorm75");
      foreach (Map map in server.Maps)
      {
        foreach (Folder folder in map.Folders)
        {
          foreach (Attachment attachment in folder.Attachments)
          {
            string fileName = "C:\\temp\\" + folder.FolderId + attachment.FileName;
            Console.WriteLine("Processing " + fileName);
            attachment.SaveToFile(fileName);
          }
        }
      }
    }
  }
}

Sunday, March 22, 2009

When will Delphi die?

I have a theory. As with all my theories, it probably doesn’t stand up to close scrutiny but I’ll put it out there none the less. And this is it. The more trouble a company is in, the more marketing emails they will send out. and if they are in really big trouble, then they start phoning people. 

If this theory is in any way correct then Embarcadero (purchasers of Delphi) are in big trouble, or their Delphi division is at least. Not only do I constantly receive emails from them or their partners telling me how great the new version of Delphi is, but I’ve also had at least three phone calls from people desperate to sell me Delphi licenses. Unfortunately I have no interest in buying Delphi licenses, since I haven’t touched Delphi code for years. Ten years ago Delphi was a great product, it beat Visual Studio hands down for its power to create Windows apps. But along came .NET, which in its original incarnation was fairly decent and has been getting better and better as time goes on. Now Delphi is essentially an irrelevance, except for people needing to maintain an existing code base. Who in their right mind would choose Delphi to build a new product? At this point I was going to direct you to the JobStats page showing demand for Delphi skills in the UK but they don’t even bother to list it anymore. There may be a small niche area where native compilation is required for performance reasons and Delphi may fit the bill, but I suspect this niche is getting smaller and smaller. Another problem that Embarcadero have is that it is almost impossible to make money from development tools. Microsoft essentially provide their tools as a loss leader and also provide damn good free versions. There are also lots of other good free development tools out there, so why pay for a development tool at all, particularly one for an obscure language that is no longer cool and happening? To be fair, you can pick up a fairly decent version of Delphi for free.

So the only question I have is when will Delphi die? I guess, like Cobol and FoxPro and a hundred other seemingly redundant technologies, it will continue to trudge along for a long time yet. Or perhaps I’m completely wrong and there is some place where Delphi can still be a winning technology, but I can’t think of one myself.

Saturday, March 21, 2009

Error logging in ASP.NET using a HTTP module

I wrote many moons ago about logging errors from an ASP.NET website but even then I knew there was a better way of doing it. The problem with that implementation was that it assumed the ASP.NET application was under my control. What happens if you want to add error logging to somebody’s else application for which you don’t have the source? Or what about an application you’ve written yourself that you’re selling to other people but also using internally, and you don’t want to prescribe to your customers how they should implement their logging? What is required is a more flexible approach to logging, something that can be plugged into any ASP.NET application.

Fortunately ASP.NET provides such a mechanism, via HTTP modules. Another reason I’d never looked at implementing logging this way was that I thought it might be tricky to do, but in fact it is so simple it took me all of an hour to produce a working solution.

The first thing to do is create a new class library and add a class that implements the IHttpModule interface. This interface has only two methods, Init and Dispose. Here's how I implemented it.

  public class ErrorLogger : IHttpModule
  {
    public void Init(HttpApplication context)
    {
      context.Error += new EventHandler(context_Error);
    }

    public void Dispose()
    {
    }

    private void context_Error(object sender, EventArgs e)
    {
      Exception ex = HttpContext.Current.Server.GetLastError();
      // log the exception however you like
    }
  }

Pretty simple. The logging code isn’t shown for brevity’s sake but all the class does is hook up an event handler for the Error event and within that event handler do whatever is necessary to log the error. In our case, we email the error and put it in a database table, but you can log it however you wish. (here’s some exception logging code for you)

Now all that’s required is to drop the assembly in the app’s bin directory and tell the ASP.NET application about the HTTP module. This is done via the web.config file with the following in the <system.web> section

  <httpModules>
    <add type="Utilities.ErrorLogger" name="ErrorLoggingModule"/>
  </httpModules>

type is the .NET type and name is whatever you like. And, er, that’s it.

As you’ve probably realised HTTP modules are powerful little things that can be used to do all kinds of stuff in a flexible manner.

Update – for those not reading the comments, there’s a free logging tool called ELMAH that can do all this for you without needing to write any code.

Thursday, March 19, 2009

IE8’s compatibility view is not IE7

It would appear that IE8 has been released today which coincides with me finding out something unpleasant about its compatibility view. I’d assumed switching to compatibility view would show the web page using IE7’s rendering engine. But this doesn’t seem to be the case. Go to the Process Mapping website and try out the dropdown menus. When you move outside the boundaries of a menu it should disappear, as you’d expect. Using IE6, IE7, IE8 (native mode), FireFox, Safari and Chrome that’s exactly what happens. But in IE8’s compatibility view the menu stays where it is.

So I can only assume compatibility view is not IE7 in new clothing. Given that anybody can switch to compatibility view for whatever reason and returning to that site will stick with that setting, this means that the introduction of IE8 means rather than having one other browser to test against, we have two instead! Oh great…

Tuesday, March 17, 2009

Whatever happened to NDoc?

NDoc was one of my favourite tools for .NET. Document your assemblies with the .NET XML documentation tags, point NDoc at them and out comes a help file. It was simple to use but was also powerful enough to customize the output in most ways you’d want.

Go to the linked website and you’ll see not much has happened on the NDoc project since 2005. Unfortunately support for .NET 2 was never completed before the original author decided to give up on the project and it looks like nobody has picked up the baton to develop it further. This is understandable to an extent. After all Microsoft have come out with Sandcastle which essentially does the same thing as NDoc. But Sandcastle is difficult to love. It has no user interface and builds take forever compared to NDoc.

The user interface problem can be solved by using Sandcastle Help File Builder, which does a nice job of looking like NDoc, but is still hampered by the underlying Sandcastle technology. After much frigging around I’m still unable to figure out how to add custom HTML into my documentation (for Google Analytics and the like) on a per project basis. I can do it globally by messing with the templates but this means I have to modify the templates for each build of a particular project, which is not ideal.

Perhaps I’m missing something, but it seems sad that 4 years since development on NDoc stopped, we still don’t have something as easy to use or as fast, even with the might of Microsoft behind it. I’m not surprised the original author decided to stop working on it, apparently he was getting threatened because his support for .NET 2 wasn’t coming along fast enough… And I’m sure the thought of trying to compete with Microsoft didn’t encourage his efforts either. But Microsoft have failed to deliver IMHO so I’m still missing NDoc after all this time.

Monday, March 16, 2009

US states table for SQL Server

I couldn’t find any SQL to generate a table of US states for SQL Server though I found this for MySql, so I modified it slightly and came up with this for SQL Server. Create the table with this

CREATE TABLE States(
    StateCode char(2) NOT NULL,
    StateName varchar(250) NOT NULL,
 CONSTRAINT PK_States PRIMARY KEY CLUSTERED 
 (
    StateCode ASC
 )
)

And populate it with this

insert into States values ('AL', 'Alabama');
insert into States values ('AK', 'Alaska');
insert into States values ('AZ', 'Arizona');
insert into States values ('AR', 'Arkansas');
insert into States values ('CA', 'California');
insert into States values ('CO', 'Colorado');
insert into States values ('CT', 'Connecticut');
insert into States values ('DE', 'Delaware');
insert into States values ('DC', 'District of Columbia');
insert into States values ('FL', 'Florida');
insert into States values ('GA', 'Georgia');
insert into States values ('HI', 'Hawaii');
insert into States values ('ID', 'Idaho');
insert into States values ('IL', 'Illinois');
insert into States values ('IN', 'Indiana');
insert into States values ('IA', 'Iowa');
insert into States values ('KS', 'Kansas');
insert into States values ('KY', 'Kentucky');
insert into States values ('LA', 'Louisiana');
insert into States values ('ME', 'Maine');
insert into States values ('MD', 'Maryland');
insert into States values ('MA', 'Massachusetts');
insert into States values ('MI', 'Michigan');
insert into States values ('MN', 'Minnesota');
insert into States values ('MS', 'Mississippi');
insert into States values ('MO', 'Missouri');
insert into States values ('MT', 'Montana');
insert into States values ('NE', 'Nebraska');
insert into States values ('NV', 'Nevada');
insert into States values ('NH', 'New Hampshire');
insert into States values ('NJ', 'New Jersey');
insert into States values ('NM', 'New Mexico');
insert into States values ('NY', 'New York');
insert into States values ('NC', 'North Carolina');
insert into States values ('ND', 'North Dakota');
insert into States values ('OH', 'Ohio');
insert into States values ('OK', 'Oklahoma');
insert into States values ('OR', 'Oregon');
insert into States values ('PA', 'Pennsylvania');
insert into States values ('RI', 'Rhode Island');
insert into States values ('SC', 'South Carolina');
insert into States values ('SD', 'South Dakota');
insert into States values ('TN', 'Tennessee');
insert into States values ('TX', 'Texas');
insert into States values ('UT', 'Utah');
insert into States values ('VT', 'Vermont');
insert into States values ('VA', 'Virginia');
insert into States values ('WA', 'Washington');
insert into States values ('WV', 'West Virginia');
insert into States values ('WI', 'Wisconsin');
insert into States values ('WY', 'Wyoming');

Friday, March 06, 2009

WPF ASCII grid part 3

It’s been a while since I last posted about my efforts to write a simple WPF app. At the end of my last post I had managed to create a custom control to display my ASCII grid and things were coming along nicely. The next thing I wanted to was pretty straightforward, or so I thought… I wanted to add a property to my control that decides whether the grid shows the standard ASCII values or the extended ASCII range. To do this I thought the simplest thing to do, when the property value changed, was clear out my grid and re-add the cells with the new values. Maybe not the most efficient approach but it seemed like the simplest option since there doesn’t seem a way to access the cell contents of a grid to change the text values.

But the problem with this approach is it doesn’t really work. Changing the property value calls the code but afterwards all that can be seen is the grid lines, not the new contents of the cells. Resizing the window does show the new cell contents so I assumed this was a refresh issue. Looking in to how to refresh a WPF control came up with these possible options

  • Call Dispatcher.Invoke with a priority of DispatcherPriority.Render
  • Call InvalidateVisual
  • Call InvalidateMeasure
  • Call InvalidateArrange
  • Call Measure
  • Call OnChildDesiredSizeChanged

I’ve no idea which of these I should be calling but I tried all of them and none had any effect. So I am at something of a loss. I think the next step is to rework my code so I don’t recreate the TextBlocks I’m using to display the ASCII values and just update the Text property of the existing TextBlocks instead.

Tuesday, March 03, 2009

FEEDJIT

If you look further down this page, you’ll see a map of the world covered with what looks like the pox. Click on the map and you’ll be whisked off to the Feedjit site which shows the location of recent hits to this site and which pages were visited. I guess it isn’t really providing any more information than I can get from Google Analytics but I think it is displayed in a much more intuitive way. Are you listening Google?

Google AJAX APIs Playground

Mostly as a reminder to myself, Google have added a AJAX APIs Playground page that shows off some of the many features they provide in Google Maps, Local Search etc. There’s lots of things there that I had no idea about and you can edit the code in place to play around with the features. Sweet.