Ohayou is currently certified at Apprentice level.

Name: Johan Sundström
Member since: 2004-08-04 18:45:50
Last Login: N/A

FOAF RDF Share This

Homepage: http://www.lysator.liu.se/~jhs/

Notes:

Pike hacker. AIDO hacker. Scriptlet hacker. For fun and profit -- but not for paying the bills nowadays. Hobbyists have more fun!

As a pet project, I've been doing some food and drink statistics pages for the web game The Kingdom of Loathing, and a bunch of in-game tools too. It's been a lots of fun coming up with how much you can do using just javascript bookmarks, especially in a framed environment.

Projects

Recent blog entries by Ohayou

Syndication: RSS 2.0
21 Aug 2005 (updated 21 Aug 2005 at 03:50 UTC) »
Adding calendar navigation to your Blogger template

Not caring much for Blogger's idea of navigation for my personal diary, today I set about adding a calendar widget similar to one I have been accustomed to having last time I was blogging, in a self-hosted do-it-all-yourself environment. Lazy-bastardness has changed that situation since, but I digress -- let's get down to business, shall we?

Someone probably made a decent calendar widget in the past few years, right? Right. Good; then I won't waste a lot of time boring my head off tweaking HTML+CSS, which I, quite honestly, detest.

Okay, download and unzip the 1.0 release, and dig in. First things first, we put it in a suitable location on some hosting of our own, something like http://www.example.com/jscalendar-1.0/ (you probably have it on some different location, yadda yadda) and toss in the necessary bits to add it to your template's <head> portion. I tucked mine in just before the end of the head section, since we will be tweaking other things in the vicinity very soon anyway. So here we go; add these lines:

<style type="text/css">@import url("/skins/aqua/theme.css");</style>
<script type="text/javascript" src="/calendar.js"></script>
<script type="text/javascript" src="/lang/calendar-en.js"></script>
<script type="text/javascript" src="/calendar-setup.js"></script>
(don't forget to prepend your URL prefix to the src/url arguments!) and we now have a beautiful bit of GNU LGPL calendar code loaded and ready, just aching to get to do something for us. So, pick a spot in your template where you want the finished calendar widget to go, and add an empty <div id="calendar-container"></div> tag there. If you're eager to try out a dummy right away, by all means go ahead: temporarily add this right after the div:

<script type="text/javascript"><!--
  Calendar.setup( {flat:'calendar-container'} );
//--></script>
(Tada!)

Okay, not very interesting before it does anything; I agree. Before we continue, though, we need to tweak some settings (or you need to rewrite some of my code to fit your own preferences). First, track down Settings -> Archiving -> Archive Frequency and set it to "Daily". (You need this to get each entry in your blog listed in the page - and hence to show up in the calendar.) Then (and the order you perform these changes is important) set Settings -> Formatting -> Archive Index Date Format to 2005-08-20 (ISO YYY-MM-DD format; very nice to parse). In case you're alienated by practical date formats, don't panic; the visitor will not see these dates anyway. Setting Date Header Format will however be visible, but my example code further down will assume you did, so if you are lazy or share my preferences you'll want to have ISO dates here, too. These are the dates shown near the head of a post in your average Blogger template, and they are used by the script to detect which date should be highlighted as "this post". But now I'm getting ahead of myself.

Now dive into your template and look up the archive portion - it's probably surrounded by a pair of <MainOrArchivePage></MainOrArchivePage> tags. Replace those with a <div id="archive"></div> tag (since you want your navigation widget on all pages, including the single-day's-posts page), and since we want the archive portion easily targetable for hiding.

That could be done with a CSS style rule #archive { display:none; }, but I opted to perform the hiding from javascript instead, knowing that there still are a few who navigate the web with javascript turned off. This way, you won't condemn them to live without navigation, even though they don't get the benefit of your spiffy DHTML generated-live calendar widget. A shame, but that's their choice, and it's very decent of you to degrade gracefully.

Make sure the contents of that div contains something like

<BloggerArchives>
<li><a href="<$BlogArchiveURL$>"><$BlogArchiveName$></a></li>
</BloggerArchives>
(the <li> and any other additional tags besides the <a> tag aren't needed, but don't hurt either). Just make sure the div swallows up every bit you want to hide for the crows who can see your nifty calendar. Now drop the proof-of-concept <script> tag you might have tossed in below the other div, and finally add this chunk at the end of your <head> tag:

<script type="text/javascript"><!--
function calendar()
{
  var archive = document.getElementById( 'archive' );
  if( archive )
  {
    archive.style.display = 'none';
    var notes = {};
    var links = archive.getElementsByTagName( 'a' );
    if( !links.length ) return;
    var i, j, node, date, y, m, d;
    for( i=0; i<links.length; i++ )
    {
      node = links[i];
      date = node.innerHTML.split('-'); // YYYY-MM-DD
      y = parseInt( date[0], 10 ); if(!notes[y]) notes[y]    = {};
      m = parseInt( date[1], 10 ); if(!notes[y][m]) notes[y][m] = {};
      d = parseInt( date[2], 10 ); notes[y][m][d] = node.href;
    }
    var dates = document.getElementsByTagName( 'h2' ), thisDate;
    for( i=0; i<dates.length; i++ )
      if( dates[i].className == 'date-header' )
      {
        var ymd = dates[i].innerHTML.split('-'); // YYYY-MM-DD
        thisDate = new Date( parseInt( ymd[0], 10 ),
                             parseInt( ymd[1], 10 )-1,
                             parseInt( ymd[2], 10 ) );
        break;
      }
    top.notes = notes;
    Calendar.setup(
    {
      date          : thisDate,
      flat          : 'calendar-container',
      range         : [ parseInt(links[0].innerHTML), y ],
      showOthers    : true,
      flatCallback  : dateChanged,
      dateStatusFunc: disableDateP
    });
  }
}

// Returns true for all dates lacking a note, false or a css style for those having one. // Exception: today does not return true, even if it lacks a note. (improves navigation) function disableDateP( date, y, m, d ) { var now = new Date; if( (y == now.getFullYear()) && (m == now.getMonth()) && (d == now.getDate()) ) return false; return noteFromDate( date ) ? false : true; }

function noteFromDate( date ) { var note = top.notes[date.getFullYear()] || {}; note = note[date.getMonth()+1] || {}; return note[date.getDate()]; }

function dateChanged( calendar ) { if( calendar.dateClicked ) { var note = noteFromDate( calendar.date ); if( note ) window.location = note; } }//--></script>

and edit the <body> tag following it to read <body onload="calendar()"> -- and you're all set to go! Dates with entries on them are clickable, and you can navigate around among the years and months as you well please, without any time-consuming web server roundtrips too.

Of course, feel free to experiment, perhaps most easily with the parameters in the Calendar.setup() call, or indeed with any other aspect; having the date-header parsing code match your date listing preferences or whatnot. Another good exercise is probably trying out a layout where you add the widget as a popup rather than the flat embedded one - that way you'll get extremely nifty keyboard navigation via the arrow (and control) keys too in the widget.

Share and enjoy! Spread the love! :-)

17 Aug 2005 (updated 17 Aug 2005 at 16:43 UTC) »
Generating an M3U playlist live from a scriptlet

While browsing around remix.kwed.org for something nice to play while working today, I realized I was missing a scriptlet to make a play list of all mp3 files linked from any web page (and web plain web server directory listings in particular).

So I wrote one. I had estimated five minutes, but it probably took closer to ten, my usual with(document){ open('content-type'); write(page); close() } proving less than useful in a scriptlet context, since the document replacing also tosses away all javascript state, including the generated playlist page to be written.

Enter the very convenient RFC 2397 data: URL protocol -- replacing that bit with a location='content-type,'+escape(page), and we're set. (The Mozilla data: testsuite is a handy resource to get a quick idea of what you can do with this toy and how, if you are new to the concept. It's still not widely supported in browsers, but good browsers do.)

Enjoy! - the Generate M3U playlist scriptlet, in its.

11 Apr 2005 (updated 20 Jun 2005 at 20:32 UTC) »
Javascript: Using XPath as an XML query tool

Long story short: Mozilla: sure. IE / MSXML2 (3.0): well, given a strong stomach, perhaps.

I've been toying with a set top box user interface at work for some time now, all HTML, images, CSS and Javascript. Very non-web, non-computer feely, remote control rules all sort of thing. The target environment is very slightly spiced-up OEM, run of the mill wintel machines, IE the browser to run it on. I'm not overly happy about that last bit.

My development environment is rather Mozilla bound, mostly because there are tools useful when debugging and because IE6 gets error messages wrong, lacks the toSource method and lots of other very useful things that add up to make it a really unfriendly, sluggish environment providing no leverage.

On adding an EPG system to this beast, I decided on using the XMLTV format for the data, and threw together some sketchy parsing using the XPathEvaluator APIs kindly provided by the Mozilla people. It worked beautifully -- here's a comfy sample:

function query_xpath( node, xpath )
{
  var document = node.ownerDocument || node;
  var evaluator = new XPathEvaluator(), ANY = XPathResult.ANY_TYPE;
  var resolver = evaluator.createNSResolver( document.documentElement );
  return document.evaluate( path, node, resolver, ANY, null );
}

with which I count nodes matching some given criterion or criteria, for instance - or any other xpath functions. I did this happily enough and left the IE bit for later. Bad move. Do you think I'd be given that freedom with MSXML? I did. I am not there yet, after several hours of reading MSDN and net resources; as it turns out, to do anything but node set operations you must add some XSLT indirection to the brew, I gathered from an MSXML based tool page not kindly enclosing the only interesting bits: source code. Most annoying of them to mark it up well to get google hits from poor sods trying to get MSXML to cooperate, in my grumpy opinion.

This might be on the right track though, should I choose to still rely on doing any XPath based queries on the raw XMLTV data (untested):

function query_xpath( node, xpath )
{
  var xsl = new ActiveXObject( 'Msxml2.FreeThreadedDOMDocument.3.0' );
  xsl.loadXML('<xsl:stylesheet><xsl:template match="/"><xsl:value-of '+
	      'select="'+ path +'"/></xsl:template></xsl:stylesheet>');
  var xslt = new ActiveXObject( 'Msxml2.XSLTemplate.3.0' );
  xslt.stylesheet = xsl;
  var xml = node.ownerDocument || node;
  var proc = xslt.createProcessor();
  proc.input = xml;
  proc.transform();
  return proc.output;
}

Not pretty. Doesn't comply with the API of processing a node set only, but rather needs the entire document, and it reeks of needless computron waste.

End of rant. I feel a little better already.

7 Mar 2005 (updated 7 Mar 2005 at 00:58 UTC) »
Javascript

I've seriously started digging into Venkman now, the Mozilla project's javascript debugger and profiler. I'm sure it's a great tool if it's your own baby or if you have someone initiated around to teach you its ways, but short of that, you need to find good webpages to help you get anywhere, such as figuring out how to set a simple breakpoint. It's a bit like learning to make good use of Emacs, though in a GUI application. Striking.

Anyway, once you acquire some basic working skills, it has a lot to offer. I fell in love with the profiling tools, not so much because I tend to write javascript code in need of optimisation, but for being beautifully done. (Once you are sitting with the profiling data and have left Venkman's GUI safely behind, anyway.)

As it happens, though, I read through Svend Tofte's good guide referred above, and ended up at the BrainJar JavaScript Crunchinator. "Hey, cool hack!" I thought to myself, and tried feeding it my present work project, an application weighing in at 32 kilobyte, and it sat there for a long time grinding on it. Minutes later, it spat out a big chunk of code that started much like my own code and ended in mid air, three kilobyte short of the end of the file. Weird.

So I inspected my own source code, and found that I had commented out a block with /*...*/ just before where the crunchinator had given up, and the block ended in a // comment, inside the block comment -- and lo, the mystery was solved.

As I was curious to see if the crunched code would actually work, once that issue was resolved, I peeked on the comment stripper, decided it was beyond fixing and decided to run my own instead. After trying a regexp cut and paste approach, I was again annoyed at Javascript RegExps, for some reason not eating entire input strings (why does (.*) not match the rest of my input data? M'kay, I suppose I will read find the answer myself in ECMA-262 next time I'm bit by this and sufficiently annoyed to learn from the specification).

On the other hand, a regexp cut-and-paste solution is by rule of thumb always the wrong solution, for one reason or another, and after having given the matter some thought and made a brief inventory of the search methods on offer in javascript (thank you so much for Javascript: the Definitive Guide, David Flanagan!), I found a much more aesthetic solution built from String.search, String.indexOf and Array.join:

function removeComments( s )
{
  var found, code = [], commentStart = /\x2f[\x2f\x2a]/, commentEnd;
  while( (found = s.search( commentStart )) >= 0 )
  {
    code.push( s.substring( 0, found ) );
    if( s[++found] == '*' )
      commentEnd = '*/';
    else
      commentEnd = '\n';
    if( (found = s.indexOf( commentEnd, found )) >= 0 )
      s = s.substring( found + commentEnd.length );
    else
      s = '';
  }
  s = code.join(' ') + s;
  return s.replace( /\n/g, ' ' );
}

I paste it back into the crunchinator, fire away, and in mere seconds, the result pops up this time, no truncation to be seen. Surprised, I test it again. Sure enough, a speedy weasel indeed. I apply my newfound Venkman knowledge, sleep through the original code's 154.34 seconds worth of heavy processing (81 of which were spent in the original removeComments function), run my own version and get a lean 4.70 seconds for running the entire script. That's some mean garbage collection gains. Just to be sure I'm not measuring something irrelevant, I run the tests again in the other order. No difference worth mentioning.

I sumbit my improvements to the original author, notice that my additions just feel into the GPL (you know where to find the license, folks) and figure it's been a decent hack. Maybe someone could even learn from it. For reference, here is the original source code (don't do this at home):

function removeComments(s) {
  var lines, i, t;
  // Remove '/* ... */' comments.
  lines = s.split("*/");
  t = "";
  for (i = 0; i < lines.length; i++)
    t += lines[i].replace(/(.*)\x2f\x2a(.*)$/g, "$1 ");
  // Remove '//' comments from each line.
  lines = t.split("\n");

t = ""; for (i = 0; i < lines.length; i++) t += lines[i].replace(/([^\x2f]*)\x2f\x2f.*$/, "$1"); // Replace newline characters with spaces. t = t.replace(/(.*)\n(.*)/g, "$1 $2"); return t; }

"The results?" I hear you asking. Well, my original 32 kilobyte application weighed in at 19, a pleasing 59.8% of its original weight, without resorting to variable renaming and similar destructive modifications. It worked, after fixing only six slight misses, half of which were my own (missing end-of-line semicolons and a case of an operator on both sides of a newline). The other half were inside regexps -- two related to apostrophes and quotation marks, the last one being the regexp /  /, which had been optimised to // (...ow! -- and the rest of the line , or the rest of the script if you so prefer, was thus effectively cut off :-).

I suppose that means that another healthy exercise would be to rewrite the string literal parsing code too, but I would suspect that any improvements over the present would mean to parse by language grammar rather than crude string matching, and somehow it doesn't feel like very gratifying work. Not that I have peeked at the code, though.

3 Mar 2005 (updated 3 Mar 2005 at 17:44 UTC) »
Bookmarklet (and calendar rant)

I absolutely detest all numeric non-ISO date formats, M/D/Y probably most of all. So when I encountered the Kingdom of Loathing calendar some benevolent (albeit calendrally challenged) person had published, I did not track down said person to tell him how glad I was at finding what I was looking for and how I felt about the format in which it was published. The meld of feelings would just not make any sense, and after all, the information was both there and fairly easily deciphered. I just strongly feel that deciphering is best left to computers.

Enter today's bookmarklet (feel free to bookmark it). It will ask you which (numeric) date format to convert from, harvest all frames for dates on that format and reformat them to readable ISO YYYY-MM-DD dates. If you go with the default M/D/Y, it will find 3/2/5, being sillyspeak for yesterday, and turn it into 2005-03-02. Short dates in the future (such as 3 / 3 / 6) will be assumed to mean the corresponding date from last century. Run it a year from now you will see 2006-03-03, though. Unless your clock is off, by a lot.

Upon googling for date tables to try it out on, I found a hilarious hallmark of stupidity - an excel sheet featuring the column "Employee Start date", "m/d/y or y/m/d e.g. 5/17/2 or 2002/5/17". The web is a silly place. Let's not go there.

11 older entries...

 

Ohayou certified others as follows:

  • Ohayou certified eMBee as Journeyer
  • Ohayou certified js as Journeyer

Others have certified Ohayou as follows:

  • titus certified Ohayou as Apprentice
  • lerdsuwa certified Ohayou as Apprentice
  • zbowling certified Ohayou as Apprentice

[ Certification disabled because you're not logged in. ]

New Advogato Features

New HTML Parser: The long-awaited libxml2 based HTML parser code is live. It needs further work but already handles most markup better than the original parser.

Keep up with the latest Advogato features by reading the Advogato status blog.

If you're a C programmer with some spare time, take a look at the mod_virgule project page and help us with one of the tasks on the ToDo list!

X
Share this page