Matthew Hipkin

[2017-05-11] fif 0.2.2 released

0.2.2    Added context menu to results table allowing the opening of files &
         Fixed incorrect text in About dialog
         Replaced match whole words with exact phrase search
0.2.1   Added file viewer
0.2	First release

[2017-05-03] Colour To HTML 6.2 released


6.2      Replaced icons with something more suitable
         Added RGB and CMYK support
         Solved problems of grey edges on HSL control
         Preview font colour switches between black and white depending on panel colour
6.1      Fixed colour entry bug
         Added support for 3-digit rgb values (eg #f00)
6.0      Ported from Delphi to Lazarus with a near-complete rewrite
         Added ability to capture any colour from the screen at any time via the tray icon

[2017-04-27] Perform whois lookup using Delphi/FreePascal

This function requires synapse.

function LookupWhois(host: String): String;
  b: TTCPBlockSocket;
  rparts: TStrings;
  refer: String;
  i: Integer;
  response: TStringStream;
  rlines: TStrings;
  refer := '';
  // First connect to and find the whois server for the domain
  b := TTCPBlockSocket.Create;
  response := TStringStream.Create('');
  b.SendString(host + #13#10);
  // Find the refer: line
  rlines := TStringList.Create;
  rlines.Text := response.DataString;
  for i := 0 to rlines.Count -1 do
    if AnsiPos('refer:',rlines[i]) > 0 then
      rparts := TStringList.Create;
      ExtractStrings([':'], [], PChar(rlines[i]), rparts);
      refer := trim(rparts[1]);
  if Length(refer) < 1 then Result := 'Could not lookup whois for ' + host
    // Now we connect to the referred server
    b := TTCPBlockSocket.Create;
    b.ConvertLineEnd := false;
    // .com and .net are handled differently
    if (AnsiEndsStr('.com',host)) or (AnsiEndsStr('.net',host)) then
      b.SendString('domain ' + host + #13#10)
      b.SendString(host + #13#10);
    response := TStringStream.Create('');
    Result := response.DataString;



[2017-04-16] Detect Windows 10 using FreePascal

Here is a quick and dirty way to detect Windows 10 using FreePascal.

function IsWin10: Boolean;
 output: String;
 output := '';
 Result := false;
 if RunCommand('cmd.exe',['/C','ver'],output) then
   if AnsiPos('[Version 10',output) > 0 then Result := true;

This function requires the Process unit to be present in your uses clause.

[2017-03-29] bin64ed 1.1 released


1.1     First release of GUI tool
1.0     First release

[2017-03-27] Simple Sitemap Creator 1.3.3 released

1.3.3   Fixed startup state spanning monitors
        Improved UI layout
1.3.2   Fixed blank useragent
        Added XiControls to about dialog
        Rewrote multithreading so it's done properly
        Fixed parsing issues with links starting ./
1.3.1   Added List tab which displays found links as a simple list
        Improved overall UI responsiveness and crawl speed
1.2.1   Improved speed related issues caused by 404 checking
1.2     Fixed update notification click
        Stopped 404s being parsed and listed
        Resolved issue with parsing of root links with filenames
1.1     Fixed saving of useragent history
        Fixed saving of URL history
        Attached return key to URL textbox
1.0     Recreated project files based on Lazarus 1.5 format
        Added option to set useragent
        Added option to disable custom theme
        Added option to change editor font
        Added option to customise filetypes to be ignored
        Changed ReadMe to DOS format
0.1.9   Fixed HTTPS bug
0.1.8   More improvements to HTML parsing
        Added CSV output
        Fixed issues with anchor links
	Resolved some issues with relative links 
0.1.7   Fixed problem of parsing links that contain anchors
        Added ignore for skype: links
        Added simple sort
0.1.6   Fixed inclusion of lone anchors in output
        Fixed last known HTML parsing bugs
        Removed alpha status
0.1.5   Improved HTML parsing again
0.1.4   Added XML output
0.1.3   General bug fixes
0.1.2   UI improvements
0.1.1   Improved HTML parsing
0.1     First release

[2017-02-06] Speed testing two third party XML parsers for FreePascal and Delphi

This article will be comparing the speed of two different third party XML parsers for FreePascal/Lazarus and Delphi. I don't intend on going into great details about features as this is purely to test the speed of parsing a simple XML file.

All testing was done using FreePascal 3.0.0 64-bit using FreeBSD 10.3 on an Intel Pentium P6200 CPU @ 2.27Ghz.

The XML files used in tests are available here (100,000 entries) and here (1,000,000 entries). The XML files are simple records of random names and ages generated using my Random Name Generator (also written using FreePascal).

The the source files for the test themselves can be downloaded here.

For the test I am simply parsing the XML contents into a TList using the following record type:

  PPerson = ^TPerson;
  TPerson = record
    FirstName: String;
    SurName: String;
    Age: Byte;

MYTHcode XML Parser

I have been using MYTHcode XML Parser for many years now, its very simple to use and has always served me well. Sadly, it hasn't been updated in a very long time so if you're trying to use it in a recent version of Delphi you'll probably find it churns out rubbish - it still works perfectly in Lazarus/FreePascal however.

The MYTHcode XML Parser uses a standard style while Next do structure that you're likely to see in other XML Parsers. The parser object is created with a string of the XML as a parameter, as a result the XML is loaded into a TStringList before being parsed.

  xml := TStringList.Create;
  parser := TXMLParser.Create(xml.Text);
  while parser.Next do
    if Parser.TagType = ttBeginTag then
      if Parser.Name = 'person' then new(person);
      if Parser.Name = 'firstname' then person^.FirstName := parser.ContentCode;
      if Parser.Name = 'surname' then person^.SurName := parser.ContentCode;
      if Parser.Name = 'age' then person^.Age := StrToIntDef(parser.ContentCode,0);
    if (Parser.TagType = ttEndTag) and (Parser.Name = 'person') then

The process is quite simple: we check for an open tag, then within that we either create a new pointer to our record type or set the relevant variable to the right value. Because it parsed on a tag-by-tag basis we can always assume the <person> tag has been found in the loops before the 3 tags containing the actual values.

Finally, when we find the closing person tag we are safe to add our record to the TList.

Destructor LibXmlParser

LibXmlParser was a library I found recently when I was forced to use Delphi for a project instead of Lazarus. It does appear to be actively developed and has full support in modern Delphi versions aswell as FreePascal/Lazarus. I am using the non-Unicode version in this test.

This library takes a different approach to parsing where each loop within the scan only focuses on the current XML segment, so the first thing I had to do was create an enum to keep track of where I was within the XML:

  TCurrentTagType = (ttNone, ttFirstName, ttSurName, ttAge);

LibXmlParser does not require the XML to exist as a string before being parsed and can load direct from a file or buffer.

  parser := TXMLParser.Create;
  parser.Normalize := true;
  while parser.Scan do
    if Parser.CurPartType = ptStartTag then
      currentTag := ttNone;
      if Parser.CurName = 'person' then new(person);
      if Parser.CurName = 'firstname' then currentTag := ttFirstName;
      if Parser.CurName = 'surname' then currentTag := ttSurName;
      if Parser.CurName = 'age' then currentTag := ttAge;
    if Parser.CurPartType = ptContent then
      case currentTag of
        ttFirstName: person^.FirstName := parser.CurContent;
        ttSurName: person^.SurName := parser.CurContent;
        ttAge: person^.Age := StrToIntDef(parser.CurContent,0);
    if (Parser.CurPartType = ptEndTag) and (Parser.CurName = 'person') then

As you can see in this parser I am having to use the enum to keep track of which tag I am inside of to extract the contents - whilst this is a perfectly valid method it doesn't "feel" natural to me and could turn in to a real nightmare when handling XML that has many fields.

The tests

First up I tested both parsers with an XML file containing 100,000 records:

As you can see from the above screenshot MYTHcode XML Parser came out on top by almost half a second. I ran the test again with 1,000,000 records.

And again the MYTHcode parser came out on top although there were signs of it slowing down: LibXmlParser managed to keep its time almost exactly 10 times that of the first run but MYTHcode ran almost 1% slower with the larger dataset.


The MYTHcode parser, on the face of things, does seem to be faster than LibXmlParser although niether seem to have functionality for loading from streams which means the XML has to exist in memory before it can be parsed; this is an issue when using very large datasets. MYTHcode Parser's downside is that it doesn't appear to be actively maintained so if you're using a modern version of Delphi you'll definitely want to avoid.

[2017-01-10] Random Name Generator

Creates an XML file containing a list of random names and ages up to the value specified in TOTALCOUNT (line 10).

Requires FreePascal to build and for both firstnames.txt and surnames.txt to be in the same directory.


After installing FreePascal, simply run the following command to build:

# fpc createnameslist.pas

Then execute the created createnameslist[.exe] binary.

Example output (TOTALCOUNT = 3):

<?xml version="1.0" encoding="UTF-8" ?>

For the latest version of the source and the required text files, visit github.

[2017-01-09] Follow HTTP redirects using Ararat Synapse

Here is a nice and simple way to follow HTTP redirects using the Ararat Synapse THTTPSend class.

program followredirect;
{$mode objfpc}{$H+}
uses Classes, SysUtils, httpsend;

function HTTPGet(URL: String; UserAgent: String): String;
  l: TStrings;
  i: Integer;
  realurl: String;
  // Setup
  HTTP := THTTPSend.Create;
  HTTP.UserAgent := UserAgent;
  realurl := '';
  l := TStringList.Create;
  // Perform HTTP request
  if HTTP.HTTPMethod('GET', URL) then
    // If its a 301 or 302 we need to do more processing
    if (HTTP.ResultCode = 301) or (HTTP.ResultCode = 302) then
      // Check the headers for the Location header
      for i := 0 to HTTP.Headers.Count -1 do
        // Extract the URL
        if Copy(HTTP.Headers[i], 1, 8) = 'Location' then
          realurl := Copy(HTTP.Headers[i], 11, Length(HTTP.Headers[i]) - 11);
      writeln('Redirecting to ', realurl);
      // If we have a URL, run it through the same function
      if Length(realurl) > 1 then l.Text := HTTPGet(realurl, UserAgent);
    else l.LoadFromStream(Http.Document);
  // Return result
  Result := l.Text;
  // Clean up

procedure main;
  response: String;
  url: String;
  url := '';
  writeln('Checking ', url);
  response := HTTPGet(url, 'Mozilla/5.0 (X11; FreeBSD amd64; rv:50.0) Gecko/20100101 Firefox/50.0');


Tested with FreePascal 3.0.0 but should work fine on Delphi.

[2017-01-01] At it again

Well, as it's a new year I thought I'd have a go at this blogging lark.

Every so often I might have a thought or two about some random subject, and who knows I might even manage to post about it here!

I'm still fiddling with the system - I always liked having the latest application releases on the home page so need to decide how I'm going to integrate those into the blog.