Archive for the ‘Programming’ Category

SplitExtended – improved VB split function with group characters

Friday, March 2nd, 2012

Based on SplitEx by Chip Pearson, SplitExtended is optimised to be over double the speed, with fixes and additional features;

Features over the standard VB Split are;

  • grouping characters, no longer split strings in quotes,
  • ignore consecutive delimiters, while preserving those in  grouping characters,
  • option to remove grouping characters, start and end quotes can be removed, and
  • double grouping characters inside a grouping character is converted to single grouping characters, double quotes inside quotes are converted to single quotes.

Download modSplitExtended.bas

Code here;

Option Explicit

'====================================================================================
' SplitExtended
' By Travis Hydzik
'
' Based on SplitEx by Chip Pearson http://www.cpearson.com/Excel/Split.aspx
'
' Improvements include;
' - over double the speed of SplitEx
' - doesn't remove double Delimiters in groups
' - double GroupChar in groups treated as escaped and converted to single
'====================================================================================
Public Function SplitExtended(ByRef InString As String, _
    ByVal Delimiter As String, _
    Optional ByVal GroupChar As String = vbNullString, _
    Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
    Optional ByVal DeleteGroupCharacters As Boolean = False) As String()

    Dim arr() As String

    Dim InGroupReplace As String
    Dim consectGroupReplace As String

    Dim S As String

    S = InString
    Dim i As Long, j As Long

    If LenB(S) = 0 Then
        'string is empty so return an unbound array (similar to original Split)
    ElseIf LenB(Delimiter) = 0 Then
        'Delimiter is empty so return an array with the first element the string
        ReDim arr(0)
        arr(0) = S
    ElseIf InStrB(1, S, Delimiter, vbBinaryCompare) = 0 Then
        'Delimiter is not found in the string, return an array with the first element of the string
        ReDim arr(0)
        arr(0) = S
    Else

        'find a unique character in string s, that isn't the Delimiter
        'it can be unique AND the group character as this means there won't be any grouping
        i = -1
        Do While LenB(InGroupReplace) = 0
            i = i + 1
            'the character is unique
            If InStrB(1, S, ChrW$(i), vbBinaryCompare) = 0 Then
                'the character is not the delimiter
                If StrComp(ChrW$(i), Delimiter, vbBinaryCompare) <> 0 Then
                    InGroupReplace = ChrW$(i)
                End If
            End If
        Loop

        'if DeleteGroupCharacters is enabled, it is common to double the GroupChar if that character is needed
        'replace all consecutive group characters with consectGroupReplace
        If DeleteGroupCharacters Then
            'only if there are consecutive GroupChar
            If InStrB(1, S, GroupChar & GroupChar, vbBinaryCompare) > 0 Then
                'find a unique character in string s, that isn't the Delimiter and isn't inGroupReplace
                Do While LenB(consectGroupReplace) = 0
                    i = i + 1
                    'the character is unique
                    If InStrB(1, S, ChrW$(i), vbBinaryCompare) = 0 Then
                        'the character is not the delimiter
                        If StrComp(ChrW$(i), Delimiter, vbBinaryCompare) <> 0 Then
                            'the character is not the GroupChar
                            If StrComp(ChrW$(i), GroupChar, vbBinaryCompare) <> 0 Then
                                consectGroupReplace = ChrW$(i)
                            End If
                        End If
                    End If
                Loop

                'once the character is found, replace all double GroupChar
                S = Replace(S, GroupChar & GroupChar, consectGroupReplace, 1, -1)
            End If
        End If

        'replace any Delimiter occuring in a group with inGroupReplace
        i = InStr(1, S, GroupChar, vbBinaryCompare)
        j = InStr(i + Len(GroupChar), S, GroupChar, vbBinaryCompare)
        Do While i > 0 And j > 0
            If j > i Then
                'mid$(s, 1, i) = Replace(mid$(s, 1, i), Delimiter, inGroupReplace, 1, -1, vbBinaryCompare)
                'mid$(s, j) = Replace(mid$(s, j), Delimiter, inGroupReplace, 1, -1, vbBinaryCompare)
                Mid$(S, i, j - i) = Replace(Mid$(S, i, j - i), Delimiter, InGroupReplace, 1, -1, vbBinaryCompare)
            Else
                S = Replace(S, Delimiter, InGroupReplace, 1, -1, vbBinaryCompare)
            End If

            i = InStr(j + Len(GroupChar), S, GroupChar, vbBinaryCompare)
            j = InStr(i + Len(GroupChar), S, GroupChar, vbBinaryCompare)
        Loop

        'remove any consecutive delimiters, iteratively
        If IgnoreConsecutiveDelimiters Then
            Do While InStrB(1, S, Delimiter & Delimiter, vbBinaryCompare) > 0
                S = Replace(S, Delimiter & Delimiter, Delimiter, 1, -1, vbBinaryCompare)
            Loop
        End If

        'perform the split
        arr = Split(S, Delimiter, -1, vbBinaryCompare)

        'loop through the array and restore the special characters
        For i = 0 To UBound(arr)
            If InStrB(1, arr(i), InGroupReplace, vbBinaryCompare) > 0 Then
                arr(i) = Replace(arr(i), InGroupReplace, Delimiter, 1, -1, vbBinaryCompare)
            End If
            If DeleteGroupCharacters Then
                If InStrB(1, arr(i), GroupChar, vbBinaryCompare) > 0 Then
                    arr(i) = Replace(arr(i), GroupChar, vbNullString, 1, -1, vbBinaryCompare)
                End If
                If InStrB(1, arr(i), consectGroupReplace, vbBinaryCompare) > 0 Then
                    arr(i) = Replace(arr(i), consectGroupReplace, GroupChar, 1, -1, vbBinaryCompare)
                End If
            End If
        Next i
    End If

    SplitExtended = arr

End Function

Related posts:

  1. Visual Basic 6 – quickest way to find first/last character in string
  2. Longest Common Subsequence implemented in VBA (Visual Basic for Applications)
  3. Generic file selection window function in VBA
  4. Decimal to Binary functions in Visual Basic
  5. Review of A-PDF Page Cut – software to split a pdf page in half

Review of A-PDF Page Cut – software to split a pdf page in half

Friday, February 17th, 2012

I came into the requirement of needing to split the  pages of a pdf in half, normally this is required when you scan a book and have two pages per scan, but my specific example was a Lonely Planet digital publication which for some reason was  released similarly, as I wanted to view it on a Kindle I required single pages only.

My first trial was free software Briss, Java coded cross-platform, I did not find it intuitive at all and couldn’t get it to work, there was no way to select exactly half of the sheet, and no batch functionality.

I then moved to Page Cut by A-PDF, A-PDF have an interesting offer called “Blog it and get it” where you blog about the software and they give you a free license, hence this post, hopefully it pulls through.

Page Cut is extremely easy to use, the steps to split a page in half are;

  1. Open pdf with Page Cut
  2. Click Add a Vertical Line from the toolbar
  3. Click Apply with default settings
  4. Click Cut and Save As

That’s it you have a single page per page pdf.

Cutting a single pdf pages in half

Batch Cut Mode is even easier;

  1. Import pdf  with Page Cut
  2. Select Cut Vertical In Half
  3. Click Cut and Save As

Batch cutting multiple pdf pages in half

So what is Page Cut missing?

I would like to see some smarts; mainly for batch cuts, warnings if cuts go through words (or there isn’t enough blank white space).

The other thing needed is a column showing if the pdf is Landscape or Portait, with the option to select/deselect either. In general a landscape page will have two pages per page, a portrait page can be ignored.

As I needed this functionality, I wrote a little VB function that finds the page orientation, it does require Acrobat though. I could then filter and move to a separate folder landscape orientated pdfs to be imported into Page Cut, here’s the code;

Public Function getOrientation(ByRef gPDFPath As String) As String
    If LenB(gPDFPath) > 0 Then
        Dim acroApp, avDoc, pdDoc, pdPage
        Dim acroPoint
        Dim x As Long, y As Long

        Set acroApp = CreateObject("AcroExch.App")
        If acroApp.GetNumAVDocs = 0 Then 'no existing files
            acroApp.Hide
        End If
        Set avDoc = CreateObject("AcroExch.AVDoc")
        If avDoc.Open(gPDFPath, "Accessing PDF's") Then
            If Not avDoc.IsValid Then
                getOrientation = "Error"
                Exit Function
            End If

            Set pdDoc = avDoc.GetPDDoc()
            Set pdPage = avDoc.GetPDDoc.AcquirePage(0) 'first page

            Set acroPoint = pdPage.GetSize()
            x = acroPoint.x
            y = acroPoint.y
            Set acroPoint = Nothing

            If x >= y Then
                getOrientation = "Landscape"
            Else
                getOrientation = "Portrait"
            End If
        End If
        pdDoc.Close
        avDoc.Close True
'        If acroApp.GetNumAVDocs > 0 Then
'            acroApp.CloseAllDocs
'        End If
        acroApp.Exit
        Set acroApp = Nothing
        Set avDoc = Nothing
        Set pdDoc = Nothing
        Set pdPage = Nothing
    End If
End Function

Finally, on my search for the above functionality I tried A-PDF Explorer, what I noticed was a second folder in my Program Files and Start menu, given A-PDF make a large amount of tools, why not have a root folder A-PDF, with subfolders for each product, makes more sense.

Related posts:

  1. Generic file selection window function in VBA
  2. Rigid file manipulation functions for VBA/VBS
  3. VB6/VBA functions to convert binary string to Base64 string
  4. Word Minimize/Maximize event capture VBA
  5. Photoshop VBScript to automatically resize images

Calculate Stamp Duty with Excel

Thursday, December 22nd, 2011

A quick post of how to calculate the Stamp Duty of a property with Excel, in my example I am using the Western Australia Residential Rate Dutiable value.

Stamp Duty
Cutoff [$]  Rate [%]  Duty [$]  Formula
0           1.9       0    0
120000      2.85      2280      =C3+(A4-A3)*B3/100
150000      3.8       3135      =C4+(A5-A4)*B4/100
360000      4.75      11115     =C5+(A6-A5)*B5/100
725000      5.15      28452.5   =C6+(A7-A6)*B6/100

Property Value        500000    
Duty                  17765     =VLOOKUP(C9,A:C,3,TRUE) + (C9-VLOOKUP(C9,A:C,1,TRUE))*VLOOKUP(C9,A:C,2,TRUE)/100

Download the Excel WorkBook.

Update, next day
This also works for Individual income tax rates;

Tax Rates
Cutoff [$]  Rate [%] Tax [$]  Formula
0           0        0        0
6000        15       0        =C3+(A4-A3)*B3/100
37000       30       4650     =C4+(A5-A4)*B4/100
80000       37       17550    =C5+(A6-A5)*B5/100
180000      45       54550    =C6+(A7-A6)*B6/100

Income               100000
Tax                  24950    =VLOOKUP(C9,A:C,3,TRUE) + (C9-VLOOKUP(C9,A:C,1,TRUE))*VLOOKUP(C9,A:C,2,TRUE)/100

Related posts:

  1. VBA automatically saves Excel 2003 Workbook in compatibility mode as Excel 2007 Workbook

SyntaxHighlighter problems with jQuery 1.7.1

Friday, December 16th, 2011

Recently I upgraded my WordPress plugins to jQuery version 1.7.1 and noticed that Internet Explorer 8 was causing errors “‘null’ is null or not an object”. I isolated this to the SyntaxHighlighter Evolved WordPress plugin I was also uses.

On further Googling it seemed that there were issues with the XRegExp library and jQuery 1.7.1, reported by other uses on IE 6 to 9. Schabse Laks posted a fix to the problem on his blog, though this gave me a new error, so I have modified it slightly, modification on line 271;

RegExp.prototype.exec = function (str) {
	if (typeof(str) !== "string")
		str = String(str);
	var match = real.exec.apply(this, arguments),
		name, r2;

Download the resolved SyntaxHighlighter files here
shCore.max.js
shCore.js

RegexBuddy – a solution to the Regular Expressions (Regex) nightmare

Thursday, December 15th, 2011

For the last few months I have been using RegexBuddy, software designed to help with Regular Expressions.

After using it to create and test more than one extremely complex regular expression, I thought it was time I gave RegexBuddy some ‘big ups’.

So what is so good about RegexBuddy,

Firstly, it allows you to create regular expressions using layman’s terms, “Non printable character”, “Match between zero and unlimited times”, “Create back-reference”, etc, no longer is there a need to remember the regular expression syntax.

Secondly, I can test my regular expression with extreme ease, I can show the full matched text and any matched groupings.

Thirdly, I can directly translate the regular expression to the programming language of choice, VB, PHP, JavaScript, etc, and not have to worry about the specifics of how to form a regular expression for that specific language.

I use RegexBuddy for PHP and JavaScript, I no longer need to ‘guess’ what the regular expression will be and constantly have to upload my code to the server to try it out.

Here’s an example expression I recently used in my WordPress plugin Slimbox2 Slideshow;

Related posts:

  1. Convert Relative Links to Absolute Links with PHP Regex
  2. Online RSVP form and database with PHP, JavaScript and MySQL

MATLAB Scaled Image Normalized Cross-Correlation

Friday, November 11th, 2011

A few week’s ago I got reacquainted with an old friend MATLAB, to solve a computer image manipulation problem.

When I blog about my travels I liked to Geocode my photos and reference them on a Google Map with a marker (see here), on the photo thumbnail I had previously stamped a circle marker on the lower right corner referencing the Google Map marker (the examples below will explain it better). I decided to move away from this approach and dynamically add these circle markers with a CSS and JavaScript solution, as this was a lot cleaner.

The problem, how do I remove the circle markers from my thumbnails, without having to re-crop every single one of my photos. It is fine for landscape photos, all image programs could batch resize the thumbnail from the full size photo, but what about portrait photos, each thumbnail was a cropping of the full size photo. I could assume either the top, vertical centre or bottom of the photo, but that would most likely be different to the original thumbnail and may miss the detail of the full size photo.

The solution was Normalized Cross Correlation, MATLAB already had an example that worked well if the cropped image had not been resized, but I needed something that would allow for resized thumbnails.

My crude solution, loops from smallest to largest sized cropped thumbnails and records the maximum peak of the normalized cross correlation, the maximum would then correspond to the correct scaled image size. This worked well, though the only issue was speed, I managed to optimise the speed by stepping the size in 6 pixel intervals, I also decrease the size of the full sized photo to shorten the number of iterations.

I have called the function cropxcorr, input Template takes in the thumbnail image, input A takes in the full size image, returns C a cropped image mirroring the thumbnail image.

There is also a MATLAB script I ran to loop through the files, portrait images will be passed with cropxcorr, landscape images simply resized.

You can download the MATLAB code and see the examples below.

cropxcorr.m

image-loop.m

Example 1
Thumbnail with marker
Example 1 thumbnail with marker
Original full sized image
Example 1 original full sized image
Generated thumbnail with no marker
Example 1 thumbnail with no marker
Example 2
Thumbnail with marker
Example 2 thumbnail with marker
Original full sized image
Example 2 original full sized image
Generated thumbnail with no marker
Example 2 thumbnail with no marker
Example 3
Thumbnail with marker
Example 3 thumbnail with marker
Original full sized image
Example 3 original full sized image
Generated thumbnail with no marker
Example 3 thumbnail with no marker

Related posts:

  1. Slimbox2 Lightbox clone with automatic image resizing
  2. Slimbox2 Slideshow – WordPress plugin to add image lightbox and slideshow

Convert from Google KML to GPS Exchange Format GPX with PHP

Thursday, October 27th, 2011

Here is a quick post on converting from KML files used in Google Earth/Maps to GPX Exchange Format (GPX) with PHP.

Code is quite explanatory, change $u with the location of the KML file, code will output GPX XML.

Alternatively, download the code here.

<?php
	//enter location of KML file here
	$u = "http://code.google.com/apis/kml/documentation/KML_Samples.kml";

	function utcdate() {
		return gmdate("Y-m-d\Th:i:s\Z");
	}

	$u_parts = pathinfo($u); //array of url parts
	$u_ext = strtoupper($u_parts['extension']);
	if ($u_ext== "KML") {

		$dom_kml = new DOMDocument();
		$dom_kml->load($u);

		$dom_gpx = new DOMDocument('1.0', 'UTF-8');
		$dom_gpx->formatOutput = true;

		//root node
		$gpx = $dom_gpx->createElement('gpx');
		$gpx = $dom_gpx->appendChild($gpx);

		$gpx_version = $dom_gpx->createAttribute('version');
		$gpx->appendChild($gpx_version);
		$gpx_version_text = $dom_gpx->createTextNode('1.0');
		$gpx_version->appendChild($gpx_version_text);

		$gpx_creator = $dom_gpx->createAttribute('creator');
		$gpx->appendChild($gpx_creator);
		$gpx_creator_text = $dom_gpx->createTextNode('http://thydzik.com');
		$gpx_creator->appendChild($gpx_creator_text);

		$gpx_xmlns_xsi = $dom_gpx->createAttribute('xmlns:xsi');
		$gpx->appendChild($gpx_xmlns_xsi);
		$gpx_xmlns_xsi_text = $dom_gpx->createTextNode('http://www.w3.org/2001/XMLSchema-instance');
		$gpx_xmlns_xsi->appendChild($gpx_xmlns_xsi_text);

		$gpx_xmlns = $dom_gpx->createAttribute('xmlns');
		$gpx->appendChild($gpx_xmlns);
		$gpx_xmlns_text = $dom_gpx->createTextNode('http://www.topografix.com/GPX/1/0');
		$gpx_xmlns->appendChild($gpx_xmlns_text);

		$gpx_xsi_schemaLocation = $dom_gpx->createAttribute('xsi:schemaLocation');
		$gpx->appendChild($gpx_xsi_schemaLocation);
		$gpx_xsi_schemaLocation_text = $dom_gpx->createTextNode('http://www.topografix.com/GPX/1/0 http://www.topografix.com/GPX/1/0/gpx.xsd');
		$gpx_xsi_schemaLocation->appendChild($gpx_xsi_schemaLocation_text);

		$gpx_url = $dom_gpx->createElement('url');
		$gpx_url = $gpx->appendChild($gpx_url);
		$gpx_url_text = $dom_gpx->createTextNode($u_parts['dirname']);
		$gpx_url->appendChild($gpx_url_text);

		$gpx_time = $dom_gpx->createElement('time');
		$gpx_time = $gpx->appendChild($gpx_time);
		$gpx_time_text = $dom_gpx->createTextNode(utcdate());
		$gpx_time->appendChild($gpx_time_text);

		// placemarks
		$names = array();
		foreach ($dom_kml->getElementsByTagName('Placemark') as $placemark) {
			//name
			foreach ($placemark->getElementsByTagName('name') as $name) {
				$name  = $name->nodeValue;
				//check if the key exists
				if (array_key_exists($name, $names)) {
					//increment the value
					++$names[$name];
					$name = $name." ({$names[$name]})";
				} else {
					$names[$name] = 0;
				}
			}
			//description
			foreach ($placemark->getElementsByTagName('description') as $description) {
				$description  = $description->nodeValue;
			}
			foreach ($placemark->getElementsByTagName('Point') as $point) {
				foreach ($point->getElementsByTagName('coordinates') as $coordinates) {
					//add the marker
					$coordinate = $coordinates->nodeValue;
					$coordinate = str_replace(" ", "", $coordinate);//trim white space
					$latlng = explode(",", $coordinate);

					if (($lat = $latlng[1]) && ($lng = $latlng[0])) {
						$gpx_wpt = $dom_gpx->createElement('wpt');
						$gpx_wpt = $gpx->appendChild($gpx_wpt);

						$gpx_wpt_lat = $dom_gpx->createAttribute('lat');
						$gpx_wpt->appendChild($gpx_wpt_lat);
						$gpx_wpt_lat_text = $dom_gpx->createTextNode($lat);
						$gpx_wpt_lat->appendChild($gpx_wpt_lat_text);

						$gpx_wpt_lon = $dom_gpx->createAttribute('lon');
						$gpx_wpt->appendChild($gpx_wpt_lon);
						$gpx_wpt_lon_text = $dom_gpx->createTextNode($lng);
						$gpx_wpt_lon->appendChild($gpx_wpt_lon_text);

						$gpx_time = $dom_gpx->createElement('time');
						$gpx_time = $gpx_wpt->appendChild($gpx_time);
						$gpx_time_text = $dom_gpx->createTextNode(utcdate());
						$gpx_time->appendChild($gpx_time_text);

						$gpx_name = $dom_gpx->createElement('name');
						$gpx_name = $gpx_wpt->appendChild($gpx_name);
						$gpx_name_text = $dom_gpx->createTextNode($name);
						$gpx_name->appendChild($gpx_name_text);

						$gpx_desc = $dom_gpx->createElement('desc');
						$gpx_desc = $gpx_wpt->appendChild($gpx_desc);
						$gpx_desc_text = $dom_gpx->createTextNode($description);
						$gpx_desc->appendChild($gpx_desc_text);

						//$gpx_url = $dom_gpx->createElement('url');
						//$gpx_url = $gpx_wpt->appendChild($gpx_url);
						//$gpx_url_text = $dom_gpx->createTextNode($ref);
						//$gpx_url->appendChild($gpx_url_text);

						$gpx_sym = $dom_gpx->createElement('sym');
						$gpx_sym = $gpx_wpt->appendChild($gpx_sym);
						$gpx_sym_text = $dom_gpx->createTextNode('Waypoint');
						$gpx_sym->appendChild($gpx_sym_text);
					}
				}
			}
			foreach ($placemark->getElementsByTagName('LineString') as $lineString) {
				foreach ($lineString->getElementsByTagName('coordinates') as $coordinates) {
					//add the new track
					$gpx_trk = $dom_gpx->createElement('trk');
					$gpx_trk = $gpx->appendChild($gpx_trk);

					$gpx_name = $dom_gpx->createElement('name');
					$gpx_name = $gpx_trk->appendChild($gpx_name);
					$gpx_name_text = $dom_gpx->createTextNode($name);
					$gpx_name->appendChild($gpx_name_text);

					$gpx_trkseg = $dom_gpx->createElement('trkseg');
					$gpx_trkseg = $gpx_trk->appendChild($gpx_trkseg);

					$coordinates = $coordinates->nodeValue;
					$coordinates = preg_split("/[\s\r\n]+/", $coordinates); //split the coords by new line
					foreach ($coordinates as $coordinate) {
						$latlng = explode(",", $coordinate);

						if (($lat = $latlng[1]) && ($lng = $latlng[0])) {
							$gpx_trkpt = $dom_gpx->createElement('trkpt');
							$gpx_trkpt = $gpx_trkseg->appendChild($gpx_trkpt);

							$gpx_trkpt_lat = $dom_gpx->createAttribute('lat');
							$gpx_trkpt->appendChild($gpx_trkpt_lat);
							$gpx_trkpt_lat_text = $dom_gpx->createTextNode($lat);
							$gpx_trkpt_lat->appendChild($gpx_trkpt_lat_text);

							$gpx_trkpt_lon = $dom_gpx->createAttribute('lon');
							$gpx_trkpt->appendChild($gpx_trkpt_lon);
							$gpx_trkpt_lon_text = $dom_gpx->createTextNode($lng);
							$gpx_trkpt_lon->appendChild($gpx_trkpt_lon_text);

							$gpx_time = $dom_gpx->createElement('time');
							$gpx_time = $gpx_trkpt->appendChild($gpx_time);
							$gpx_time_text = $dom_gpx->createTextNode(utcdate());
							$gpx_time->appendChild($gpx_time_text);
						}
					}
				}
			}
		}
		header("Content-Type: text/xml");
		echo $dom_gpx->saveXML();
	}
?>

Let me know if you find any issues.

Related posts:

  1. thydzik Google Map v2.0 released – rewritten code supporting API v3 and gpx downloads!
  2. Convert Relative Links to Absolute Links with PHP Regex
  3. VB6/VBA functions to convert binary string to Base64 string
  4. RSS feed aggregator/combiner in PHP with Magpie RSS (v2)
  5. Dynamic Google Maps markers/icons with PHP

Convert Relative Links to Absolute Links with PHP Regex

Sunday, October 23rd, 2011

Here is a quick post on how to find and replace relative links with absolute links using PHP Regular Expressions (regex).

$url = "http://myabsoluteurl.com/";
$ret = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $ret);

Related posts:

  1. PHP Factorial and Combination functions
  2. RSS feed aggregator/combiner in PHP with Magpie RSS (v2)
  3. Dynamic Google Maps circle markers/icons with PHP