x
Showing posts with label others. Show all posts
Showing posts with label others. Show all posts

Monday, 30 September 2013

Tracking your DHL package in conky

(or converting the DHL tracking page from HTML to plain text, using XSLT)

So, let me explain the problem: You have that package shipped by DHL, and its tracking number. And you're so eager to receive it, that you end up checking the package tracking page every 5 minutes. Your productivity falls to zero.

But, worry no more! Let's put the package status inside your conky, so that you can just have a quick look on the side of your screen, and continue working.

Just in case you don't know (but really, you should), conky is the information bar on the right of the screenshot below:
conky is the information bar on the right on the display. CPU/RAM usage, disk free space, network status, temperature, a calendar, my DHL tracking thing, then weather in a few places.
In case you wonder, and the background is somewhere in Hue, Vietnam.
And don't worry about the gimp error, really.
The idea is to write the code that can generate this:
Yes, they spelled my name wrong...

From something as ugly looking as this:

I don't care how it works, I just want to get it running

Ok! After all the point of this was to increase your productivity, right? You can fetch the script from my github.

Then call it with:
./dhl <AWB>
Where <AWB> is the Waybill number (tracking number). It produces a text-only tracking information for your package.

You can integrate it in your conky with something like:
${font Monospace:size=6}${execi 60 ~/.conky/dhl <AWB> | head -n 3 | fold -w 16}$font

Replace ~/.conky/dhl with the path to where you copied the script. Change head parameter if you want more lines, and fold inserts new lines every 16 characters (change that depending on your conky width).

Now, if you want to know how it works, so you can fix it if it breaks, or update the code for other shipping companies, continue reading.

Inspecting the HTML source

The tracking URL looks like this (where <AWB> is your tracking number):
http://www.dhl-usa.com/content/us/en/express/tracking.shtml?brand=DHL&AWB=<AWB>
Looking at the HTML source, we notice that the interesting stuff is enclosed in a table:
<table border="0" summary="Summary of table content">
Then, you have a succession of thead/tbody tags. The first thead contains general information about the package, that we are not interested in. It starts like this (notice it has class "tophead"):
<thead class="tophead">
The next thead shows the date valid for the following entries. We are only interested in the first column here (the one that contains the date).
<thead>
    <tr>
        <td colspan="5" class="emptyRow"></td>
    </tr>
    <tr>
        <th scope="col" colspan="2" axis="length"
            style="width: 40% ;text-align:left">Thursday, September 19, 2013         </th>
        <th scope="col" axis="length"
            style="width: 30% ;text-align:left ">Location</th>
        <th scope="col" axis="length"
            style="width: 9%;text-align:left">Time</th>
        <th scope="col" axis="length" class="lastChild"
            style="width: 25% ;text-align:left">&nbsp;</th>
    </tr>
</thead>
Finally, the bulk of the events are enclosed in tbody. The first column is a incremented number, the second one is a description of what happened (passed customs, arrived at destination, etc.), the third one tell you the location (but this is often repeated in the description), and the fourth one is the time.
<tbody>
    <tr>
        <td class="" style="width: 5% ;text-align:left">18</td>
        <td class="" style="text-align:left">With delivery courier</td>
        <td class="" style="text-align:left">SINGAPORE - SINGAPORE</td>
        <td class="">7:27 PM</td>
        <td class="lastChild "><!--start contentteaser -->
        <div class="dhl">
        <div><div class="clearAll">&nbsp;</div></div>
        </div><!--end contentteaser --></td>
    </tr>
</tbody>
Ok, now we have an idea of the structure, let's parse that!

Parse HTML with XSLT

Ok, so let's say you have the DHL tracking page downloaded to /tmp/dhl.tmp, and an XSLT file in dhl.xslt, you can parse the page with:
xsltproc --html dhl.xslt /tmp/dhl.tmp
The XSLT file looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" encoding="utf-8" />
    <xsl:template match="/">
        <xsl:for-each select="//table[@summary='Summary of table content']/*[self::thead|self::tbody][not(@class)]">
            <xsl:choose>
                <xsl:when test="name(.) = 'thead'">
                    <xsl:value-of select="tr/th[1]"/>
                    <xsl:text>
</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:if test="floor(tr/td[1]) = tr/td[1]">
                        <xsl:value-of select="normalize-space(tr/td[4])"/>
                        <xsl:text>: </xsl:text>
                        <xsl:value-of select="normalize-space(tr/td[2])"/>
                        <xsl:text>
</xsl:text>
                    </xsl:if>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>
Let's take it step by step. It starts like this:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="text" encoding="utf-8" />
    <xsl:template match="/">
Nothing special here, apart from the text output mode, so that xsltproc outputs a text file (and not another XML file...).

Now begins the fun. We look for a table with summary attribute 'Summary of table content'. Inside that table, we look for thead and tbody elements, that do not have a class attribute set, so we can exclude the 'tophead' row, that we are not interested in.
<xsl:for-each select="//table[@summary='Summary of table content']/*[self::thead|self::tbody][not(@class)]">
Now, thead (containing only the date of the following events) and tbody (containing events) need to be parsed differently. This is done with xsl:choose:
<xsl:choose>
    <xsl:when test="name(.) = 'thead'">
...
    </xsl:when>
    <xsl:otherwise>
...
    </xsl:otherwise>
</xsl:choose>
For thead, we just want to show the date, that is the first th inside a tr (tr/th[1]). Then we print a new line with xsl:text.
<xsl:when test="name(.) = 'thead'">
    <xsl:value-of select="tr/th[1]">
    <xsl:text>
</xsl:text>
</xsl:value-of></xsl:when>
For tbody, it is slightly more complicated. First, we check that the first column is indeed a number (this removes the last row in the table, which is another type of summary): this is done with a "trick" (floor(tr/td[1]) = tr/td[1]). Then we print the time (4th column), followed by a colon, and the event description (2nd column).
<xsl:otherwise>
    <xsl:if test="floor(tr/td[1]) = tr/td[1]">
        <xsl:value-of select="normalize-space(tr/td[4])"/>
        <xsl:text>: </xsl:text>
        <xsl:value-of select="normalize-space(tr/td[2])"/>
        <xsl:text>
</xsl:text>
    </xsl:if>
</xsl:otherwise>
That's it! Then you can put everything in a shell script, see the complete code on github for details.

It's so cool, I want more!

I get it. I, too, have become of fan of parsing XML/HTML from scripts. See this post for another example.

Wednesday, 19 June 2013

Good morning haze!

Yesterday morning Singapore woke up under thick haze due to forest fires in nearby Sumatra. Not healthy: You can feel it in your throat, and some corridors smell like Scamorza (some delicious Italian smoked cheese).

It smells just like that... (Image from Necrophorus@Wikipedia, GFDL)
Anyway... it gives some "interesting" light when the sun is low.

Good morning purée... (slightly underexposed)


Red sun, still high above the horizon (~1h before sunset).

Sunday, 2 June 2013

Chromebook

Since the adventure during my last trip, where my (work/home/main) laptop suddenly died, I realized that carrying a heavy and expensive laptop is not the best idea while travelling (in terms of risk of theft, damage, but also weight to carry around).

A tablet would be an option, but they do not offer the flexibility I want (keyboard, photo editing, etc.), and they are generally not easy to hack. Netbooks seems to be a thing of the past, so I went for a Chromebook.

I went for the second cheapest (Samsung ARM Chromebook, USD 249), the cheapest being a bit too heavy and bulky (Acer C7, USD 199), and maybe less exciting with a standard x86 processor.

It is unfortunately not available in Singapore (at least not through usual channels), and some websites, such as Amazon, do not want to ship it to Singapore. Fortunately for me, B&H Photo Video does not care, and shipped it to me for USD 52. Three days later, I received my new toy, with no GST to pay (total amount below SGD 400, see here).

Samsung ARM Chromebook (running Archlinux/XFCE using chroagh, but more on that later).
Well, I won't repeat what other reviews say online (there are plenty), but, basically:
  • First, you pay what you get for. Don't expect it to behave like a laptop with a price tag 10 times higher (a friend said: "Fake Macbook!! Oh no, Korean!!", and, well, that's not completely inaccurate): The body is plastic, and this is not a powerhouse. But still, for the price, I find it awesome.
  • Very light (1.1kg) and thin (1.8cm).
  • No fan, no moving parts: No noise at all, little heat, and long battery life (~6h).
  • Dual-core ARM processor (1.7Ghz): This is a bit better than your last generation cell phone (well, it does out-of-order execution so it's probably a step faster), but it seems to do the job fairly well.
  • Chrome OS:
    • Very good browsing experience, very fast. Youtube works well in general, but tends to lag when you do other things at the same time: not ideal if you have your music playlist on Youtube.
    • You need connectivity for most stuff. There are a few offline apps (I haven't tried many), but most things will require a Wifi connection.
  • Movie playback (from files, non-Youtube) is a problem: It could not read some x264-encoded files, even in SD quality. I mean, it could, but only displayed one frame per second or so... Maybe this could be improved in later versions of Chrome OS by optimizing the video decoder (e.g. use whatever hardware acceleration facilities the processor has).
  • Finally, and this is critical for me: hackability. This is not an Apple device or a locked Android phone. Google lets you do whatever you want with your computer, including wiping Chrome OS to install something else, or installing another Linux in parallel (crouton/chroagh).
In short, mildly recommended for the normal user, Chrome OS is great, but really needs a working connection to be used to its full potential. And this is not always the case while travelling.

Strongly recommended for the more advanced user, as you can install a regular Linux in parallel: either as Dual-boot, or as a chroot inside Chrome OS, where you can switch between Chrome OS and your Linux with a simple key combination. I used the wonderful crouton to install Ubuntu, and ported it to support Archlinux (chroagh). More on that for a later post.

Friday, 1 March 2013

Random blog banner - Part 2

Alright, the previous part shows you the script that allows to randomly display a banner image. Now, this would quickly end up being a bit messy to manage, if you want to add a new banner, then update the page with the list of all panoramas.

I wrote a Ruby script to semi-automate the process. The source can be found on github.

We first start with a database of images, called images.db. It is a YAML file, with this format:
- url:    https://lh4.googleusercontent.com/.../s{}/xx.jpg
  width:  800
  repeat: true
  offset: -30
  text:   Krabi - Thailand
  date:   2012.12
I like to use YAML, because it is easy to parse in Ruby, without being as wordy as XML, but more structured than a simple text file.

The fields are:
  • url: source image location. s{} in the URL will be replaced by the proper image width
  • width: width to display
  • repeat: if the panorama is a full 360° that can be repeated
  • offset: vertical offset to center the image
  • text: Text that will appear in the banner, and in the image description
  • date: year and month when the image was taken
Then, the script banner.rb reads images.db. It generates the Javascript arrays, and insert them in jscode.template, creating a new jscode.js file, that you can then copy-paste on the blog.

It will also read pagecode.html.template, and generate pagecode.html for the list of images.

That's it! Looking at the code should be somehow self-documenting, if you want to modify it. I release the code in public domain, do whatever you want with it!

Tuesday, 26 February 2013

Random blog banner - Part 1

You probably have noticed: the banner image of this blog changes every time you refresh the page. Those are stitched panorama from many pictures, sometimes spanning a full 360 degrees, sometimes less (I will write something about stitching these panoramas at some point...).

The code to do randomize the banner image is easily available online, I took it from this page, but it is found in so many places that I'm not sure who wrote it first.

My updated version follows:
<script type="text/javascript">
var banner= new Array()
var shift= new Array()
var information= new Array()
banner[0]="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhNXX68HJ-zGPBvn2ZPOIPfpV0tnIJ8yXM9yxoKh3FEW9ycY0883_oXfM2TNYOis319rf2H2YvI-hloChdei3v1o8yXiNaBqnvPRktWXBjooDsFqJKtpz0pxXASYPc98QMuzvnlKpArbJCL/s1600/101P.jpg"
shift[0]="no-repeat center -50px"
information[0]="Jiufen - Taiwan"

//...
var random=Math.floor(banner.length*Math.random());
var query = window.location.search.substring(1);
var match = query.match(/.*headerimage=([0-9]+).*/);
if (match) {
document.write("Manual image index:");
document.write(match[1]);
random = match[1];
}
document.write("<style>");
document.write(".body-fauxcolumn-outer .cap-top {");
document.write(' background: #FFFFFF url("' + banner[random] + '") ' + shift[random] + ';');
document.write(" }");
document.write("</style>");
document.getElementById('headerinformation').innerHTML = information[random];
</script>
Let's take it step by step. I define 3 arrays: banner, shift, and information, each index representing one image:
  • banner contains the URL of the image. I extract the public link from the Picasa Web Albums. This URL can be obtained by a right-click on the image, and then "View Image". The URL looks like this: https://lh6.googleusercontent.com/.../s1600/X.jpg. The number after the letter s is particularly interesting: it defines the width of the image. I you change it, Google will very nicely resize it to the size you want. Using s0 gives you the original resolution.
  • shift contains some extra CSS style information. For full 360° panoramas, the image can be repeated endlessly without seems, so the parameter repeat is used, otherwise, the panorama is shown only once. Also, you can set an offset, so that the image gets centered vertically, so you don't get only sky or only land/sea in the banner.
  • information contains some short description about the image.
Then, I pick a random number in the list, unless the URL is something like http://drinkcat.blogspot.com/?headerimage=0 : In that case I force displaying image 0. That's useful for debugging.

Finally, the code generates a little piece of CSS style, that will set the banner image. Also, it modifies the HTML element with id headerinformation, so that it shows the image information. In my case, this element is at the bottom of the banner, aligned to the right, and tells you where the image was taken.

All right. All for now, next post will show how to generate the script, as well as this page, automatically.

Friday, 18 January 2013

Nikon Charger vs "Nikon" Charger

On the topic of charger vs charger, and how they are not all born equal, here is another one.

This one is a battery charger, for a Nikon Coolpix camera. The original one refused to charge, while in India. The Nikon centre over there opened it, and apparently fixed it. However, it still refused to charge the battery, so we bought another one (on eBay, again...).

Let's compare the 2 chargers. From the outside, they look pretty similar:

Left: eBay charger. Right: original charger. (click to zoom)

Tuesday, 8 January 2013

Picasa as (random) data storage

People have been using Gmail storage space for backup purpose, GmailFS is one of such projects. What I propose here is to use Google Picasa: Storage space is unlimited, as long as your image dimensions do not exceed 2048x2048 pixels. By encoding data to appear as a image, you could potentially get unlimited storage space.

Friday, 21 December 2012

Dell Charger vs "Dell" Charger

Not all chargers are created equal.
--- A drinking cat

My 3-year old Dell charger had cable issues for a while now. The inner wires of the cable that goes from the charger to the laptop are exposed. The cable from the mains to the charger also broke once, but that part is easy and cheap to replace.
Exposed wires on the charger.

I did not worry too much about it until, one day, I had to fiddle the wires to get my laptop to charge... I got afraid that it would stop working without warning, leaving me with 1 hour of battery life, and then, no computer to work (and play, and blog ,-)).