<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>masochismtango &#187; Web</title>
	<atom:link href="http://masochismtango.com/web/feed/" rel="self" type="application/rss+xml" />
	<link>http://masochismtango.com</link>
	<description></description>
	<lastBuildDate>Wed, 28 Jul 2010 12:45:12 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.1</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Verified by Visa</title>
		<link>http://masochismtango.com/2010/02/25/verified-by-visa/</link>
		<comments>http://masochismtango.com/2010/02/25/verified-by-visa/#comments</comments>
		<pubDate>Thu, 25 Feb 2010 11:31:51 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[3DS]]></category>
		<category><![CDATA[BA]]></category>
		<category><![CDATA[broken]]></category>
		<category><![CDATA[Verified by Visa]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=525</guid>
		<description><![CDATA[Verified by Visa - even more annoying when it doesn't work.]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/525.png&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>Plenty of people have commented on the stupidity of Verified by Visa and similar schemes, which put iframed verification forms into the web purchasing system that look remarkably like phishing forms (encouraging users to trust such embedded forms), <a href="http://news.zdnet.co.uk/security/0,1000000189,40008732,00.htm">without providing any additional security benefits</a>.  A quick glance at the economics explains why sites do this &#8211; it enables them to lower their costs by shunting fraud risk onto Visa &#8211; but from a user&#8217;s perspective it&#8217;s still bloody stupid.</p>
<p>Even worse is when you cannot complete a payment without it, but it doesn&#8217;t work &#8211; an experience I have just had with BA, trying to book a flight over to Queen&#8217;s Day in Amsterdam.  After entering all my details, I got this:<br />
<a href="http://masochismtango.com/wp-content/uploads/2010/02/verified-by-visa.png"><img src="http://masochismtango.com/wp-content/uploads/2010/02/verified-by-visa-281x300.png" alt="" title="Verified by Visa" width="281" height="300" class="aligncenter size-medium wp-image-526" /></a></p>
<p>The bank would like the following information&#8230; an empty iframe.  It&#8217;s actually loading a JSP on BA&#8217;s site which delivers an empty HTML page wrapping a script that tries to trigger a form that isn&#8217;t defined in the markup.  Knowing that is no great consolation&#8230;</p>
<p>So congratulations BA, Easyjet were undoubtedly very happy to receive some cash in exchange for a functional web experience.</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2010/02/25/verified-by-visa/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>WebHarvest: Easy Web Scraping from Java</title>
		<link>http://masochismtango.com/2010/02/15/webharvest-web-scraping-from-java/</link>
		<comments>http://masochismtango.com/2010/02/15/webharvest-web-scraping-from-java/#comments</comments>
		<pubDate>Mon, 15 Feb 2010 10:06:23 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Maven]]></category>
		<category><![CDATA[regex]]></category>
		<category><![CDATA[scrape]]></category>
		<category><![CDATA[Tableau]]></category>
		<category><![CDATA[WebHarvest]]></category>
		<category><![CDATA[XPath]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=478</guid>
		<description><![CDATA[A quick guide to WebHarvest's easy and powerful web scraping Java API, including code samples.]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/478.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>I&#8217;ve been experimenting with data visualisation for a while now, most of which is for <a href="http://www.masabi.com/">Masabi</a>&#8217;s business plan though I hope to share some offshoots soon.</p>
<p>I often have a need to quickly scrape some data out of a web page (or list of web pages), which can then be fed into Excel and on to specialist data visualisation tools like Tableau (available in a <a href="http://www.tableausoftware.com/public/">free public edition</a> here &#8211; my initial impressions are positive but it&#8217;s early days yet).</p>
<p>To this end I have turned to <a href="http://web-harvest.sourceforge.net/">WebHarvest</a>, an excellent scriptable open source API for web scraping in Java.  I really really like it, but there are some quirks and setup issues that have cost me hours so I thought I&#8217;d roll together a tutorial with the fixes.</p>
<h3>WebHarvest Config for Maven</h3>
<p>When it works <a href="http://maven.apache.org/">Maven</a> is a lovely tool to hide dependency management for Java projects, but WebHarvest is not configured qiute right out of the box to work transparently with it.  (Describing Maven is beyond the scope of this post, but if you don&#8217;t know it, it&#8217;s easy to setup with the <a href="http://m2eclipse.sonatype.org/">M2 plugin for Eclipse</a>.)</p>
<p>This is the Maven POM I ended up with to use WebHarvest in a new JavaSE project:</p>
<pre name="code" class="xml">&lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"&gt;
 &lt;modelVersion&gt;4.0.0&lt;/modelVersion&gt;
 &lt;groupId&gt;WebScraping&lt;/groupId&gt;
 &lt;artifactId&gt;WebScraping&lt;/artifactId&gt;
 &lt;packaging&gt;jar&lt;/packaging&gt;
 &lt;version&gt;0.00.01&lt;/version&gt;
 &lt;properties&gt;
 &lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
 &lt;/properties&gt;

 &lt;build&gt;
 &lt;plugins&gt;
 &lt;plugin&gt;
 &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
 &lt;configuration&gt;
 &lt;source&gt;1.6&lt;/source&gt;
 &lt;target&gt;1.6&lt;/target&gt;
 &lt;/configuration&gt;
 &lt;/plugin&gt;
 &lt;/plugins&gt;
 &lt;/build&gt;

 &lt;repositories&gt;
 &lt;repository&gt;
 &lt;id&gt;wso2&lt;/id&gt;
 &lt;url&gt;http://dist.wso2.org/maven2/&lt;/url&gt;
 &lt;/repository&gt;
 &lt;repository&gt;
 &lt;id&gt;maven-repository-1&lt;/id&gt;
 &lt;url&gt;http://repo1.maven.org/maven2/&lt;/url&gt;
 &lt;/repository&gt;
 &lt;/repositories&gt;
 &lt;dependencies&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;commons-logging&lt;/groupId&gt;
 &lt;artifactId&gt;commons-logging&lt;/artifactId&gt;
 &lt;version&gt;1.1&lt;/version&gt;
 &lt;type&gt;jar&lt;/type&gt;
 &lt;scope&gt;compile&lt;/scope&gt;
 &lt;/dependency&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;log4j&lt;/groupId&gt;
 &lt;artifactId&gt;log4j&lt;/artifactId&gt;
 &lt;version&gt;1.2.12&lt;/version&gt;
 &lt;type&gt;jar&lt;/type&gt;
 &lt;scope&gt;compile&lt;/scope&gt;
 &lt;/dependency&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;org.webharvest.wso2&lt;/groupId&gt;
 &lt;artifactId&gt;webharvest-core&lt;/artifactId&gt;
 &lt;version&gt;1.0.0.wso2v1&lt;/version&gt;
 &lt;type&gt;jar&lt;/type&gt;
 &lt;scope&gt;compile&lt;/scope&gt;
 &lt;/dependency&gt;
 &lt;!-- web harvest pom doesn't track dependencies well --&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;net.sf.saxon&lt;/groupId&gt;
 &lt;artifactId&gt;saxon-xom&lt;/artifactId&gt;
 &lt;version&gt;8.7&lt;/version&gt;
 &lt;/dependency&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;org.htmlcleaner&lt;/groupId&gt;
 &lt;artifactId&gt;htmlcleaner&lt;/artifactId&gt;
 &lt;version&gt;1.55&lt;/version&gt;
 &lt;/dependency&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;bsh&lt;/groupId&gt;
 &lt;artifactId&gt;bsh&lt;/artifactId&gt;
 &lt;version&gt;1.3.0&lt;/version&gt;
 &lt;/dependency&gt;
 &lt;dependency&gt;
 &lt;groupId&gt;commons-httpclient&lt;/groupId&gt;
 &lt;artifactId&gt;commons-httpclient&lt;/artifactId&gt;
 &lt;version&gt;3.1&lt;/version&gt;
 &lt;/dependency&gt;
 &lt;/dependencies&gt;
&lt;/project&gt;</pre>
<p>You&#8217;ll note that the WebHarvest dependencies had to be added explicitly, because the jar does not come with a working pom listing them.</p>
<h3>Writing A Scraping Script</h3>
<p>WebHarvest uses XML configuration files to describe how to scrape a site &#8211; and with a few lines of Java code you can run any XML configuration and have access to any properties that the script identified from the page.  This is definitely the safest way to scrape data, as it decouples the code from the web page markup &#8211; so if the site you are scraping goes through a redesign, you can quickly adjust the config files without recompiling the code they pass data to.</p>
<p>The site  some good <a href="http://web-harvest.sourceforge.net/samples.php">example scripts</a> to show you how to get started, so I won&#8217;t repeat them here.  The easiest way to create your own is to run the WebHarvest GUI from the command line, start with a sample script, and then hack it around to get what you want &#8211; it&#8217;s an easy iterative process with good feedback in the UI.</p>
<p>As a simple example, this is a script to go to the <a href="http://developer.sonyericsson.com/device/searchDevice.do?restart=true">Sony-Ericsson developer site&#8217;s handset gallery</a> at <code>http://developer.sonyericsson.com/device/searchDevice.do?restart=true</code>, and rip each handset&#8217;s individual spec page URI:</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;config&gt;
	&lt;!-- indicates we want a loop, through the list defined in &lt;list&gt;, doing &lt;body&gt; for each item where the variables uri and i are defined as the index and value of the relevant item --&gt;
	&lt;loop item="uid" index="i"&gt;
		&lt;!-- the list section defines what we will loop over - here, it pulls out the value attribute of all option tags --&gt;
		&lt;list&gt;
			&lt;xpath expression="//option/@value"&gt;
				&lt;html-to-xml&gt;
					&lt;http url="http://developer.sonyericsson.com/device/searchDevice.do?restart=true"/&gt;
				&lt;/html-to-xml&gt;
			&lt;/xpath&gt;
		&lt;/list&gt;
		&lt;!-- the body section lists instructions which are run for every iteration of the loop --&gt;
		&lt;body&gt;
			&lt;!-- we define a new variable for every iteration, using the iteration count as a suffix  --&gt;
			&lt;var-def name="uri.${i}"&gt;
				&lt;!-- template tag is important, else the $ var syntax will be ignored and won't do any value substitutions --&gt;
				&lt;template&gt;device/loadDevice.do?id=${uid}&lt;/template&gt;
			&lt;/var-def&gt;
		&lt;/body&gt;
	&lt;/loop&gt;
&lt;/config&gt;</pre>
<p>The handset URIs will end up in a list of variables, from <code>uri.1</code> to <code>uri.N</code>.</p>
<p>The XML configuration&#8217;s syntax can take a little getting used to &#8211; it appeared quite backwards to me at first, but by messing around in the GUI you can experiment and learn pretty fast.  With a basic understanding of <a href="http://www.w3schools.com/XPath/default.asp">XPath</a> to identify parts of the web page, and perhaps a little <a href="http://www.regular-expressions.info/tutorial.html">regular expression knowledge</a> to get at information surrounded by plain text, you can perform some very powerful scraping.</p>
<p>We can then define another script which will take this URI, and pull out a piece of information from the page &#8211; in this example, it will show the region(s) that the handset was released in:</p>
<pre name="code" class="xml">&lt;?xml version="1.0" encoding="UTF-8"?&gt;
&lt;config&gt;
	&lt;!-- get the entire page --&gt;
	&lt;var-def name="wholepage"&gt;
		&lt;html-to-xml&gt;
			&lt;!-- NEVER try and pass in the entire URL as a single variable here! --&gt;
			&lt;http url="http://developer.sonyericsson.com/${uri}"/&gt;
		&lt;/html-to-xml&gt;
	&lt;/var-def&gt;
	&lt;!-- rip out the block with the specifications --&gt;
	&lt;var-def name="specsheet"&gt;
		&lt;xpath expression="//div[@class='phone-specs']"&gt;
			&lt;var name="wholepage"/&gt;
			&lt;/xpath&gt;
		&lt;/var-def&gt;
		&lt;!-- find the handset's name --&gt;
	&lt;var-def name="name"&gt;
		&lt;xpath expression="//h5[contains(text(),'Phone Model')]/following-sibling::p[1]/text()"&gt;
			&lt;var name="specsheet"/&gt;
			&lt;/xpath&gt;
	&lt;/var-def&gt;
	&lt;!-- identify the screen resolution --&gt;
	&lt;regexp&gt;
		&lt;regexp-pattern&gt;([\d]*)x([\d]*)&lt;/regexp-pattern&gt;
			&lt;regexp-source&gt;
				&lt;xpath expression="//h5[contains(text(),'Screen Sizes')]/following-sibling::p[1]/text()"&gt;
					&lt;var name="specsheet"/&gt;
				&lt;/xpath&gt;
			&lt;/regexp-source&gt;
		&lt;regexp-result&gt;
			&lt;var-def name="screen.width"&gt;&lt;template&gt;${_1}&lt;/template&gt;&lt;/var-def&gt;
			&lt;var-def name="screen.height"&gt;&lt;template&gt;${_2}&lt;/template&gt;&lt;/var-def&gt;
		&lt;/regexp-result&gt;
	&lt;/regexp&gt;
&lt;/config&gt;</pre>
<p>At this point I should note the biggest gotcha with WebHarvest, that just caused me 3 hours of hear tearing.  In the script, this line defines the page to scrape: <code>&lt;http url="http://developer.sonyericsson.com/<strong>${uri}</strong>"/&gt;</code>, where <code>${uri}</code> is a variable specified at runtime to define a URI.  This works.</p>
<p>If you were to substitute in this perfectly sensible alternative: <code>&lt;http url="<strong>${url}</strong>"/&gt;</code>, you would end up with a completely obscure runtime exception a little like this:</p>
<pre><code>Exception in thread "main" org.webharvest.exception.ScriptException: Cannot set variable in scripter: Field access: bsh.ReflectError: No such field: 1
	at org.webharvest.runtime.scripting.BeanShellScriptEngine.setVariable(Unknown Source)
	at org.webharvest.runtime.scripting.ScriptEngine.pushAllVariablesFromContextToScriptEngine(Unknown Source)
	at org.webharvest.runtime.scripting.BeanShellScriptEngine.eval(Unknown Source)
	at org.webharvest.runtime.templaters.BaseTemplater.execute(Unknown Source)
	at org.webharvest.runtime.processors.TemplateProcessor.execute(Unknown Source)
	at org.webharvest.runtime.processors.BaseProcessor.run(Unknown Source)
	at org.webharvest.runtime.processors.BodyProcessor.execute(Unknown Source)
	at org.webharvest.runtime.processors.VarDefProcessor.execute(Unknown Source)
	at org.webharvest.runtime.processors.BaseProcessor.run(Unknown Source)
	at org.webharvest.runtime.processors.BodyProcessor.execute(Unknown Source)
	at org.webharvest.runtime.processors.BaseProcessor.run(Unknown Source)
	at org.webharvest.runtime.processors.LoopProcessor.execute(Unknown Source)
	at org.webharvest.runtime.processors.BaseProcessor.run(Unknown Source)
	at org.webharvest.runtime.Scraper.execute(Unknown Source)
	at org.webharvest.runtime.Scraper.execute(Unknown Source)
	at scrape.QuickScraper.scrapeUrlList(QuickScraper.java:82)
	at scrape.QuickScraper.scrapeUrlList(QuickScraper.java:49)
	at scrape.ActualScraper.main(DhfScraper.java:37)
Caused by: Field access: bsh.ReflectError: No such field: 1 : at Line: -1 : in file: <Called from Java Code> : <Compiled Java Code>

	at bsh.UtilEvalError.toEvalError(Unknown Source)
	at bsh.UtilEvalError.toEvalError(Unknown Source)
	at bsh.Interpreter.set(Unknown Source)
	... 18 more
</code></pre>
<p>You have been warned!</p>
<h3>Running The Scripts From Java</h3>
<p>WebHarvest requires very little code to run.  I created this little reusable harness class to quickly run the two types of script &#8211; one to pull information from a page, and one to farm URLs from which to scrape data.  You can use the first without the second, of course.</p>
<pre name="code" class="java">package scrape;

import java.io.*;
import java.util.*;

import org.apache.commons.logging.*;
import org.webharvest.definition.ScraperConfiguration;
import org.webharvest.runtime.*;
import org.webharvest.runtime.variables.Variable;

/**
 * Quick hackable web scraping class.
 * @author Tom Godber
 */
public abstract class QuickScraper
{
	/** Logging object. */
	protected final Log LOG = LogFactory.getLog(getClass());
	/** Prefix for any variable scraped which defines a URL. It will be followed by a counter. */
	public static final String SCRAPED_URL_VARIABLE_PREFIX = "url.";
	/** A variable name which holds the initial URL to scrape. */
	public static final String START_URL_VARIABLE = "url";

	/** A temporary working folder. */
	private File working = new File("temp");

	/** Ensures temp folder exists.` */
	public QuickScraper()
	{
		working.mkdirs();
	}

	/**
	 * Scrapes a list of URLs which are automatically derived from a page.
	 * The initial URL must be set in the actual URL list config XML.
	 * @param urlConfigXml Path of an XML describing how to scrape the URL list.
	 * @param pageConfigXml Path of an XML describing how to scrape the individual pages found.#
	 * @return The number of URLs processed, or -1 if the config could not be loaded.
	 */
	protected int scrapeUrlList(String urlConfigXml, String pageConfigXml)
	{
		return scrapeUrlList(new HashMap(), urlConfigXml, pageConfigXml);
	}

	/**
	 * Scrapes a list of URLs which are automatically derived from a page.
	 * @param setup Optional configuration for the script
	 * @param urlConfigXml Path of an XML describing how to scrape the URL list.
	 * @param pageConfigXml Path of an XML describing how to scrape the individual pages found.#
	 * @return The number of URLs processed, or -1 if the config could not be loaded.
	 */
	protected int scrapeUrlList(Map setup, String urlConfigXml, String pageConfigXml)
	{
		return scrapeUrlList(setup, new File(urlConfigXml), new File(pageConfigXml));
	}

	/**
	 * Scrapes a list of URLs which are automatically derived from a page.
	 * The initial URL must be set in the actual URL list config XML.
	 * @param urlConfigXml XML describing how to scrape the URL list.
	 * @param pageConfigXml XML describing how to scrape the individual pages found.#
	 * @return The number of URLs processed, or -1 if the config could not be loaded.
	 */
	protected int scrapeUrlList(File urlConfigXml, File pageConfigXml)
	{
		return scrapeUrlList(new HashMap(), urlConfigXml, pageConfigXml);
	}

	/**
	 * Scrapes a list of URLs which are automatically derived from a page.
	 * @param setup Optional configuration for the script
	 * @param urlConfigXml XML describing how to scrape the URL list.
	 * @param pageConfigXml XML describing how to scrape the individual pages found.
	 * @return The number of URLs processed, or -1 if the config could not be loaded.
	 * @throws NullPointerException If the setup map is null.
	 */
	protected int scrapeUrlList(Map setup, File urlConfigXml, File pageConfigXml)
	{
		try
		{
			if (LOG.isDebugEnabled())	LOG.debug("Starting scrape with temp folder "+working.getAbsolutePath()+"...");
			// generate a one-off scraper based on preloaded configuration
			ScraperConfiguration config = new ScraperConfiguration(urlConfigXml);
			Scraper scraper = new Scraper(config, working.getAbsolutePath());
			// initialise any config
			setupScraperContext(setup, scraper);
			// run the script
			scraper.execute();

			// rip the URL list out of the scraped content
			ScraperContext context = scraper.getContext();
			int i=1;
			Variable scrapedUrl;
			if (LOG.isDebugEnabled())	LOG.debug("Scraping performed, pulling URLs '"+SCRAPED_URL_VARIABLE_PREFIX+"n' from "+context.size()+" variables, starting with "+i+"...");
			while ((scrapedUrl = (Variable) context.get(SCRAPED_URL_VARIABLE_PREFIX+i))  != null)
			{
				if (LOG.isTraceEnabled())	LOG.trace("Found "+SCRAPED_URL_VARIABLE_PREFIX+i+": "+scrapedUrl.toString());
				// parse this URL
				setup.put(START_URL_VARIABLE, scrapedUrl.toString());
				scrapeUrl(setup, pageConfigXml);
				// move on
				i++;
			}
			if (LOG.isDebugEnabled())	LOG.debug("No more URLs found.");
			return i;
		}
		catch (FileNotFoundException e)
		{
			if (LOG.isErrorEnabled())	LOG.error("Could not find config file '"+urlConfigXml.getAbsolutePath()+"' - no scraping was done for this WebHarvest XML.", e);
			return -1;
		}
		finally
		{
			working.delete();
		}
	}

	/**
	 * Scrapes an individual page, and passed the results on for processing.
	 * The script must contain a hardcoded URL.
	 * @param configXml XML describing how to scrape an individual page.
	 */
	protected void scrapeUrl(File configXml)
	{
		scrapeUrl((String)null, configXml);
	}

	/**
	 * Scrapes an individual page, and passed the results on for processing.
	 * @param url The URL to scrape. If null, the URL must be set in the config itself.
	 * @param configXml XML describing how to scrape an individual page.
	 */
	protected void scrapeUrl(String url, File configXml)
	{
		Map setup = new HashMap();
		if (url!=null)	setup.put(START_URL_VARIABLE, url);
		scrapeUrl(setup, configXml);
	}

	/**
	 * Scrapes an individual page, and passed the results on for processing.
	 * @param setup Optional configuration for the script
	 * @param configXml XML describing how to scrape an individual page.
	 */
	protected void scrapeUrl(Map setup, File configXml)
	{
		try
		{
			if (LOG.isDebugEnabled())	LOG.debug("Starting scrape with temp folder "+working.getAbsolutePath()+"...");
			// generate a one-off scraper based on preloaded configuration
			ScraperConfiguration config = new ScraperConfiguration(configXml);
			Scraper scraper = new Scraper(config, working.getAbsolutePath());
			setupScraperContext(setup, scraper);
			scraper.execute();

			// handle contents in some way
			pageScraped((String)setup.get(START_URL_VARIABLE), scraper.getContext());

			if (LOG.isDebugEnabled())	LOG.debug("Page scraping complete.");
		}
		catch (FileNotFoundException e)
		{
			if (LOG.isErrorEnabled())	LOG.error("Could not find config file '"+configXml.getAbsolutePath()+"' - no scraping was done for this WebHarvest XML.", e);

		}
		finally
		{
			working.delete();
		}
	}

	/**
	 * @param setup Any variables to be set before the script runs.
	 * @param scraper The object which does the scraping.
	 */
	private void setupScraperContext(Map<String,Object> setup, Scraper scraper)
	{
		if (setup!=null)
			for (String key : setup.keySet())
				scraper.getContext().setVar(key, setup.get(key));
	}

	/**
	 * Process a page that was scraped.
	 * @param url The URL that was scraped.
	 * @param context The contents of the scraped page.
	 */
	public abstract void pageScraped(String url, ScraperContext context);
}
</pre>
<p>Scraping a new set of data then becomes as simple as extending the class, passing in appropriate config, and pulling out whatever variables you want every time a page is scraped:</p>
<pre name="code" class="java">package scrape;

import org.webharvest.runtime.ScraperContext;
import org.webharvest.runtime.variables.Variable;

public class ActualScraper extends QuickScraper
{
	public static void main(String[] args)
	{
		try
		{
			ActualScraper scraper = new ActualScraper();
			// do the scraping
			scraper.scrapeUrlList(config, "config/se.urls.xml", "config/se.page.xml");
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	/**
	 * @see scrape.QuickScraper#pageScraped(java.lang.String, org.webharvest.runtime.ScraperContext)
	 */
	public void pageScraped(String url, ScraperContext context)
	{
		Variable nameVar = context.getVar("name");
		if (nameVar==null)
		{
			if (LOG.isWarnEnabled())	LOG.warn("Scrape for "+url+" produced no data! Ignoring");
			return;
		}

		// store this station's details
		if (LOG.isInfoEnabled())	LOG.info(name+" has "+context.getVar("screen.width").toString()+"x"+context.getVar("screen.height").toString()+" screen");
	}
}
</pre>
<p>Soi there you have it &#8211; a powerful, configurable and highly effective web scraping system with almost no code written!</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2010/02/15/webharvest-web-scraping-from-java/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Portfolio: Corzano e Paterno.com</title>
		<link>http://masochismtango.com/2010/01/26/portfolio-corzano-e-paterno-com/</link>
		<comments>http://masochismtango.com/2010/01/26/portfolio-corzano-e-paterno-com/#comments</comments>
		<pubDate>Tue, 26 Jan 2010 12:00:57 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Corzano e Paterno]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=396</guid>
		<description><![CDATA[
A trilingual Wordpress site built for the Corzano e Paterno farm in Tuscany, producing prize winning wines, olive oil and cheeses.  It&#8217;s an old farm in the hills that used to be owned by the Machievellis, which now has a lovely set of guest houses you can rent through the site.

Picasa photo album integration:

The [...]]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/396.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>A <a href="/2009/06/07/multilingual-wordpress-sites/">trilingual</a> Wordpress site built for the <a href="http://www.corzanoepaterno.com/">Corzano e Paterno farm</a> in Tuscany, producing prize winning <a href="http://www.corzanoepaterno.com/wine/">wines</a>, <a href="http://www.corzanoepaterno.com/wine/olive-oil/">olive oil</a> and <a href="http://www.corzanoepaterno.com/cheese/">cheeses</a>.  It&#8217;s an old farm in the hills that used to be owned by the Machievellis, which now has a lovely set of <a href="http://www.corzanoepaterno.com/agriturismo/">guest houses</a> you can rent through the site.</p>
<p style="text-align: center;"><a href="http://www.corzanoepaterno.com/wine/"><img src="http://masochismtango.com/wp-content/uploads/2010/01/CeP-wine.jpg" alt="Corzano e Paterno site - wine page" /></a></p>
<p>Picasa photo album integration:</p>
<p style="text-align: center;"><a href="http://www.corzanoepaterno.com/wine/photos/"><img src="http://masochismtango.com/wp-content/uploads/2010/01/CeP-photo-album.jpg" alt="Corzano e Paterno site - example photo album" /></a></p>
<p>The site features a blog, press clippings, downloads of publications which have featured the farm&#8217;s wines, and other news:</p>
<p style="text-align: center;"><a href="http://www.corzanoepaterno.com/news/"><img src="http://masochismtango.com/wp-content/uploads/2010/01/CeP-news.jpg" alt="Corzano e Paterno site - news page" /></a></p>
<p>This is integrated into a database of each year&#8217;s wines and cheeses:</p>
<p style="text-align: center;"><a href="http://www.corzanoepaterno.com/cheese/"><img src="http://masochismtango.com/wp-content/uploads/2010/01/CeP-cheese.jpg" alt="Corzano e Paterno site - cheese page" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2010/01/26/portfolio-corzano-e-paterno-com/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Portfolio: Masabi Rebrand</title>
		<link>http://masochismtango.com/2009/12/10/portfolio-masabi-rebrand/</link>
		<comments>http://masochismtango.com/2009/12/10/portfolio-masabi-rebrand/#comments</comments>
		<pubDate>Thu, 10 Dec 2009 19:43:33 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Mobile]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[branding]]></category>
		<category><![CDATA[Masabi]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=407</guid>
		<description><![CDATA[A complete rebrand for my company, Masabi, as we transition from a general mobile consultancy to a complete focus on the transport ticketing industry.  This involved a total branding refresh, new Wordpress-based web site and related brand collateral.]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/407.png&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>My company has gone through a complete transition over the second half of 2009, moving from a general mobile application consultancy to a product-based transport ticketing vendor.  This new focus merited a total branding overhaul as <a href="/2008/08/14/portfolio-masabi-web-site/">our old look</a>, with its black background, was more appropriate for our legacy marketing and gaming background.</p>
<p>The new font and colour scheme were designed to evoke the feel of the old British Rail branding, whilst the logo resembles the front of an Intercity train:</p>
<p style="text-align:center"><a href="http://masochismtango.com/wp-content/uploads/2009/12/logo.png"><img class="aligncenter size-full wp-image-411" title="Masabi's new logo - The Ticket Machine In Your Pocket" src="http://masochismtango.com/wp-content/uploads/2009/12/logo.png" alt="Masabi's new logo - The Ticket Machine In Your Pocket" width="298" height="94" /></a></p>
<p>The new tagline &#8211; &ldquo;The Ticket Machine In Your Pocket&rdquo; &#8211; came out of a brainstorming session during the excellent <a href="http://www.g2i.org/">g2i</a> (Gateway to Investment) course we took part in, which I would highly recommend to anyone interested in grooming their company for funding, or just understanding when a startup needs funds and what to expect from investors.  It&#8217;s sponsored by the London Development Authority but run by industry professionals, offering top quality advice and opportunities where all participant&#8217;s interests are aligned &#8211; far better than the fee-based &#8216;advice&#8217; and &#8216;connections&#8217; that are so easy to come by.</p>
<p style="text-align:center"><a href="http://www.masabi.com/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-front.jpg" alt="" title="Masabi.com new front page" width="400" height="474" class="aligncenter size-full wp-image-412" /></a></p>
<p>The front page embeds a video of the product in action which really explains the underlying concept nicely &#8211; the photos I took during the video shoot now form a great resource of imagery for company documents and presentations:<br />
<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="560" height="340" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowFullScreen" value="true" /><param name="allowscriptaccess" value="always" /><param name="src" value="http://www.youtube.com/v/lwhelQ9Df0o&amp;hl=en&amp;fs=1&amp;" /><param name="allowfullscreen" value="true" /><embed type="application/x-shockwave-flash" width="560" height="340" src="http://www.youtube.com/v/lwhelQ9Df0o&amp;hl=en&amp;fs=1&amp;" allowscriptaccess="always" allowfullscreen="true"></embed></object></p>
<p>The site structure is intentionally simple: it features simple product tours aimed at <a href="http://www.masabi.com/tour/passengers/">Passengers</a> and <a href="http://www.masabi.com/tour/operators/">Train Operating Companies</a>:</p>
<table width="100%" border="0">
<tr>
<td align="center" valign="top">
<a href="http://www.masabi.com/tour/passengers/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-tour-passenger.jpg" alt="" title="Masabi site - tour for passengers" width="200" height="238" class="aligncenter size-full wp-image-414" /></a>
</td>
<td align="center" valign="top">
<a href="http://www.masabi.com/tour/operators/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-tour-operator.jpg" alt="" title="Masabi site - tour for train operators" width="200" height="236" class="aligncenter size-full wp-image-413" /></a>
</td>
</tr>
</table>
<p>The <a href="http://www.masabi.com/news/">news section</a> manages press releases and external coverage, alongside a social media feed integrating the company&#8217;s <a href="http://www.flickr.com/photos/masabi">Flickr</a>, <a href="http://twitter.com/Masabi_com">Twitter</a>, <a href="http://www.youtube.com/user/MasabiVideos">YouTube</a> and <a href="http://www.slideshare.net/masabi">SlideShare</a> channels:</p>
<p style="text-align:center"><a href="http://www.masabi.com/news/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-news.jpg" alt="" title="masabi news" width="400" height="439" class="aligncenter size-full wp-image-415" /></a></p>
<p>There is also a live feed showing the next event Masabi will be presenting at driven by our Google-based events calendar, with an <a href="http://www.masabi.com/news/events">integrated view on the site</a>:</p>
<p style="text-align:center"><a href="http://masochismtango.com/wp-content/uploads/2009/12/masabi-calendar.jpg"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-calendar.jpg" alt="" title="masabi calendar" width="400" height="287" class="aligncenter size-full wp-image-420" /></a></p>
<p>The company <a href="http://www.masabi.com/blog/">blog</a> was migrated over from the Blogger account of the old site; a <a href="http://masochismtango.com/2009/10/25/migrating-blogger-to-wordpress-easy-301-permalink-redirects/">redirect plugin was set up</a> to ensure legacy URLs continued to work:</p>
<table width="100%" border="0">
<tr>
<td align="center" valign="top">
<a href="http://www.masabi.com/blog/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-blog.jpg" alt="" title="Masabi site - blog" width="200" height="524" class="aligncenter size-full wp-image-419" /></a>
</td>
<td align="center" valign="top">
<a href="http://www.masabi.com/blog/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-blog-post.jpg" alt="" title="Masabi site - example blog post" width="200" height="633" class="aligncenter size-full wp-image-418" /></a>
</td>
</tr>
</table>
<p>The site also has all the obvious bells and whistles like Google Maps integration to find the office, and directions from the nearest tube stations etc:</p>
<p style="text-align:center"><a href="http://www.masabi.com/find-us/"><img src="http://masochismtango.com/wp-content/uploads/2009/12/masabi-find.jpg" alt="" title="masabi find" width="400" height="420" class="aligncenter size-full wp-image-421" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/12/10/portfolio-masabi-rebrand/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Migrating Blogger to Wordpress &#8211; Easy 301 Permalink Redirects</title>
		<link>http://masochismtango.com/2009/10/25/migrating-blogger-to-wordpress-easy-301-permalink-redirects/</link>
		<comments>http://masochismtango.com/2009/10/25/migrating-blogger-to-wordpress-easy-301-permalink-redirects/#comments</comments>
		<pubDate>Sun, 25 Oct 2009 14:41:58 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Dev]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Blogger]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=355</guid>
		<description><![CDATA[This psot explains how to automatically set up the Wordpress Redirection plugin to map imported Blogger post permalinks to their new Wordpress post permalinks.]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/355.gif&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>I&#8217;ve been moving the Masabi web site and blog onto Wordpress, from a combination of static web content and a blog driven by Blogger.  Wordpress has a great import function to move the posts across, which does most of the initial work for you.</p>
<p>However, Wordpress won&#8217;t by itself set up redirects for the old Blogger to new Wordpress permalinks.  The two platforms shrink post titles to URLs differently, so it&#8217;s not as simple as matching Wordpress permalink structures to Blogger&#8217;s under the Settings.</p>
<p>I did see one plugin which was supposed to migrate Blogger permalinks automatically, but it didn&#8217;t work and also didn&#8217;t cover the full scope I needed &#8211; I also have legacy static html links to remap into a totally different site structure.  To achieve this I turned to the excellent <a href="http://urbangiraffe.com/plugins/redirection/">Redirection plugin</a> from John Godley.</p>
<h3>Getting Inside The Database</h3>
<p>The plugin allows you to manually set up redirects with a very friendly interface, but there&#8217;s no fun migrating 70 blog posts by hand.  This is where SQL can come to our rescue!</p>
<p>VERY IMPORTANT: take a <a href="http://codex.wordpress.org/WordPress_Backups">full backup of your database</a> before you start messing around with SQL.  In theory this is a pretty low risk operation, but, you never know!</p>
<p>The Blogger import utility saves custom fields for every imported post; the Blogger permalink is held in the <code>blogger_permalink</code> field; custom fields are stored on the <code>post_meta</code> table with an obvious ID based backlink to the original table.  This makes one half of the mapping very easy to set up.</p>
<p>The other half is slightly more subtle, because Wordpress permalinks are not actually stored on the <code>wp_posts</code> table.  Every post does have a GUID, but this is based on what its permalink was when you did the import &#8211; and if you imported when you created the blog and set your Wordpress permalinks later, this will not reflect the post&#8217;s current permalink URI.  Ideally we&#8217;d like the 301 to point to the real end URI, so we need to get a little creative and rebuild the permalink in the way Wordpress does it, from the post metadata.</p>
<p>My permalink structure looks like this:<br />
<code>YYYY/MM/DD/title/</code></p>
<p>This can be rebuilt using the following string manipulation in SQL:<br />
<code>CONCAT('/',YEAR(post_date),'/',LPAD(MONTH(post_date),2,'0'),'/',LPAD(DAY(post_date),2,'0'),'/',post_name,'/')</code></p>
<p>Notes on the SQL functions:</p>
<ul>
<li><code>CONCAT</code> just combines all of its arguments together into a single string;</li>
<li><code>LPAD</code> is used to pad the left of the string with 0s, as the month and day are always 2 digits long;</li>
<li><code>YEAR</code>, <code>MONTH</code> and <code>DAY</code> extract the relevant fields from the post&#8217;s creation date/time.</li>
</ul>
<p>Given this data, we can easily create an automatic import SQL statement for moving the data across:</p>
<pre class="sql">INSERT INTO wp_redirection_items (url,action_data,regex,group_id,status,action_type,action_code,match_type,last_access,position)
SELECT M.meta_value AS url,CONCAT('/',YEAR(P.post_date),'/',LPAD(MONTH(P.post_date),2,'0'),'/',LPAD(DAY(P.post_date),2,'0'),'/',P.post_name,'/') AS action_data,0 AS regex,1 AS group_id,'enabled' AS status,'url' AS action_type,301 AS action_code,'url' AS match_type, 0 AS last_access, 69 as position
FROM wp_postmeta M, wp_posts P
WHERE M.meta_key='blogger_permalink' AND M.post_id=P.ID AND P.post_status='publish';</pre>
<p>Run this through PHPMyAdmin, refresh the Redirection admin page, and you should now find that all of your permalinks have been moved across.  Note that we set the position field to an arbitrary constant, here 69, so we can easily delete the inserted rows if we messed up and then try again, without upsetting any other redirects already set up.</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/10/25/migrating-blogger-to-wordpress-easy-301-permalink-redirects/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>E-Mail Not Working In PHP / Wordpress? It May Be CPanel MX Records&#8230;</title>
		<link>http://masochismtango.com/2009/10/20/e-mail-not-working-in-php-wordpress-it-may-be-cpanel-mx-records/</link>
		<comments>http://masochismtango.com/2009/10/20/e-mail-not-working-in-php-wordpress-it-may-be-cpanel-mx-records/#comments</comments>
		<pubDate>Tue, 20 Oct 2009 09:36:06 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[CPanel]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[PHP]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=344</guid>
		<description><![CDATA[
I host a few domains through Nativespace and Host Gator (which both use the CPanel interface), and all of them have had one flaw &#8211; PHP apps like Wordpress, Drupal, and also Wordpress plugins like the Dagon Design Mailer Script have never been able to send email.  It&#8217;s quite hard to track down this kind [...]]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/344.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>I host a few domains through <a href="http://www.nativespace.co.uk/">Nativespace</a> and <a href="http://www.hostgator.com/">Host Gator</a> (which both use the <a href="http://www.cpanel.net/">CPanel</a> interface), and all of them have had one flaw &#8211; PHP apps like Wordpress, Drupal, and also Wordpress plugins like the <a href="http://www.dagondesign.com/articles/secure-php-form-mailer-script/">Dagon Design Mailer Script</a> have never been able to send email.  It&#8217;s quite hard to track down this kind of error yourself on a managed host with a web UI so in the past I&#8217;d not worried about it, but finally it came time to solve it (or rather get Ben to solve it, but I&#8217;ll write about it so others don&#8217;t have to go through the hard work)</p>
<h2>The Problem: CPanel&#8217;s Broken MX Assumptions</h2>
<p>Note: you can ignore this section if you don&#8217;t care <em>why </em>the problem occurs, and just want a fix!</p>
<p>It transpires that the problem occurs when the domain being hosted is registered through some other provider, which therefore runs the domain&#8217;s <a href="http://en.wikipedia.org/wiki/Domain_Name_System">DNS</a> and <a href="http://en.wikipedia.org/wiki/MX_record">MX records</a>.  MX records tell the world how your domain runs its e-mail.</p>
<p>Rather than do a proper MX lookup for sending e-mail, CPanel just assumes it runs the domain&#8217;s MX records and looks up in its own local configuration.  This would be fine if it did run the domain&#8217;s e-mail, but in these instances it doesn&#8217;t and therefore the e-mail ends up falling into a black hole.  Why the authors chose to do this is uncertain, but it is easy to fix.</p>
<h2>The Fix: Redundanct Configuration</h2>
<p>First, find the MX Entry icon in the Mail section of the CPanel front page:</p>
<p style="text-align: center;"><img class="aligncenter" src="/wp-content/uploads/2009/10/cpanel-mx.jpg" alt="CPanel's MX Entry icon" /></p>
<p>Click on it to see CPanel&#8217;s configuration for your domain.  The default looks something like this:</p>
<p style="text-align: center;"><a href="/wp-content/uploads/2009/10/cpanel-wrong-mx.gif"><img class="aligncenter" src="/wp-content/uploads/2009/10/cpanel-wrong-mx.gif" alt="CPanel's default MX configuration" width="530" /></a></p>
<p>Many of my domains use the free <a href="http://www.google.com/apps/intl/en/group/index.html">Google Apps product</a> to offer GMail accounts that work with the domain (ie. a GMail inbox tied to an @masochismtango.com address).  Here is the CPanel configuration you need if your domain does the same &#8211; if not, you&#8217;ll need to check your MX records with your domain registrar to find the right configuration to enter here.</p>
<p>Warning: this solution only works <em>if you know the real MX records for the domain!</em> If you don&#8217;t you could mess things up.  Remember it may be easier to just file a support ticket &#8211; I&#8217;m not taking responsibility if you mess this up!</p>
<p>First, add the correct MX configuration for the domain:</p>
<p style="text-align: center;"><a href="/wp-content/uploads/2009/10/cpanel-right-mx.gif"><img src="/wp-content/uploads/2009/10/cpanel-right-mx.gif" alt="MX configuration for a domain with Google Apps GMail" width="530" /></a></p>
<p>For GMail, you can get away with just adding a couple of entries, at priority 1 and 5.  Then, delete the old priority 0 entry which CPanel started with to leave just the real configuration:</p>
<p style="text-align: center;"><img src="/wp-content/uploads/2009/10/cpanel-google-mx.gif" alt="MX configuration for a domain with Google Apps GMail" /></p>
<p>Now you&#8217;re done &#8211; hopefully your emails should instantly start working!</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/10/20/e-mail-not-working-in-php-wordpress-it-may-be-cpanel-mx-records/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Portfolio: KeenCity.tv web site</title>
		<link>http://masochismtango.com/2009/09/13/portfolio-keencity-tv/</link>
		<comments>http://masochismtango.com/2009/09/13/portfolio-keencity-tv/#comments</comments>
		<pubDate>Sun, 13 Sep 2009 19:32:58 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[portfolio]]></category>

		<guid isPermaLink="false">http://masochismtango.com/2009/09/13/portfolio-keencity-tv/</guid>
		<description><![CDATA[I've just finished a quick hack of Typebased, a free Wordpress theme from Woo Themes, to nicely frame Youtube videos for KeenCity.tv.]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/326.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>I&#8217;ve just finished a quick hack of <a href="http://www.woothemes.com/2008/11/typebased/">Typebased</a>, a free Wordpress theme from Woo Themes, to nicely frame Youtube videos for <a href="http://keencity.tv/">KeenCity.tv</a>:</p>
<p style="text-align: center;"><a title="KeenCity.tv web site" href="http://keencity.tv/projects"><img src="http://masochismtango.com/wp-content/uploads/2009/09/keencity.jpg" alt="KeenCity.tv web site" /></a></p>
<p>We decided that starting from someone else&#8217;s theme was the only way to get a site up and running on a shoestring budget.</p>
<p>The header photo is one I took from the <a href="http://uk.tilllate.com/en/event/9465944">top of Centrepoint at dawn</a>, carefully edited to remove any iconic London buildings and convey the sense of an unknown city coming to life.</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/09/13/portfolio-keencity-tv/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Mobile Webapps &#8211; iUI Framework Extensions</title>
		<link>http://masochismtango.com/2009/08/03/mobile-webapps-iui-framework-extensions/</link>
		<comments>http://masochismtango.com/2009/08/03/mobile-webapps-iui-framework-extensions/#comments</comments>
		<pubDate>Mon, 03 Aug 2009 11:58:44 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Mobile]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[Android]]></category>
		<category><![CDATA[CSS]]></category>
		<category><![CDATA[iPhone]]></category>
		<category><![CDATA[iUI]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[local webapp]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=242</guid>
		<description><![CDATA[As I explained in my earlier Masabists post, I'm finally able to show off some of the work I've been doing recently on local mobile webapps.  We've based our webapps on the rather excellent iUI framework, which I have extended in the following ways...]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/242.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>As I explained in my earlier <a href="http://masochismtango.com/2009/07/31/masabists-ticketing-via-local-webapps/">Masabists post</a>, I&#8217;m finally able to show off some of the work I&#8217;ve been doing recently on local mobile webapps.  We&#8217;ve based our webapps on the rather excellent <a href="http://code.google.com/p/iui/">iUI framework</a>, which has a great philosophy:</p>
<ul>
<li>Clean, standard HTML markup;</li>
<li>Very lightweight &#8211; one CSS stylesheet and a single Javascript file;</li>
<li>Built-in AJAX support, so form values can be submitted and HTML fragment responses pasted into the document structure;</li>
<li>iPhone native-style look and feel, including slick screen to screen transitions and screen rotation support.</li>
</ul>
<p>The screens are all embedded inside a single HTML file, which has two advantages:</p>
<ol>
<li>You get very rapid movement around the app, with no need for a reliable network connection and none of the slowdown associated with downloading every page;</li>
<li>You can cache the app using an HTML 5 manifest, so even when the user has no network signal they can access the site.</li>
</ol>
<p>This is great as far as it goes, but there were a number of screen types not supported.  I have added an optional extension script, following the same philosophy, to add them &#8211; this ost is just a reference point to describe what the extensions do, when discussing merging them into the framework.</p>
<h2>Structure</h2>
<p>The extensions are in a seperate script and CSS file, which creates the <code>window.iui_ext</code> object, much like the core iUI framework itself.</p>
<p>To work, iUI has been extended in one key way &#8211; it now fires off Javascript events to screens, specifically:</p>
<ul>
<li><code>onFocus()</code> is called whenever a screen is made visible;</li>
<li><code>onBlur()</code> is called whenever a screen is left.</li>
</ul>
<p>Both events are called at the point where the link is clicked and the sliding transition begins.</p>
<p>Adding the events gives the developer a huge amount of extra scope for controlling the webapp, which is then used to create the additional webapps without breaking away from clean markup.</p>
<h2>Form Handling</h2>
<p>The iUI extensions automatically add focus and blur event handlers for every form, which store all field values in a name/value store whenever a form is left and restore the values whenever a form is visited again.  These are added inside the iUI code, and once finished they will call any explicitly defined <code>onFocus="..."</code> / <code>onBlur="..."</code> attributes within the markup.</p>
<p>The map follows the same basic semantics as the <a href="http://developer.apple.com/safari/library/documentation/iPhone/Conceptual/SafariJSDatabaseGuide/Name-ValueStorage/Name-ValueStorage.html#//apple_ref/doc/uid/TP40007256-CH6-SW1">HTML 5 <code>sessionStore</code> object</a>, which I wanted to use but sadly it isn&#8217;t implemented on the iPhone yet (the latest desktop Safari seems to have it though).  The methods are exposed through the iui_ext object, for example <code>window.iui_ext.getItem('key')</code>.</p>
<p>The purpose of this store is to allow the movement of values between screens, which enables a number of tricks &#8211; by declaring <code>input</code>s on seperate <code>form</code>s (displayed as seperate screens) with the same <code>name</code> attribute, you can make sure they always mirror each other&#8217;s values.  If for any reason you don&#8217;t want this behaviour for some fields, you can declare an onBlur event and remove them from the store.</p>
<h2>Option Lists</h2>
<p>I needed a form screen which listed a number of fields which could be changes, such as railcard type in the screenshot below, and by tapping on the field the user would move to a list of all the possible options from which they could select one.  A few examples of this UI pattern from my iPod Touch Settings app are:</p>
<ul>
<li>Settings &gt; Music &gt; Audiobook Speed (list of 3 options)</li>
<li>Settings &gt; Video &gt; Start Playing (list of 2 options)</li>
<li>Settings &gt; Safari &gt; Accept Cookies</li>
</ul>
<p>Visually, it looks like this:</p>
<p style="text-align: center;"><a href="http://www.flickr.com/photos/masabi/3775148234/in/photostream/"><img title="iPhone option selection UI widget" src="http://masochismtango.com/wp-content/uploads/2009/08/optionlist.jpg" alt="iPhone option selection UI widget" /></a></p>
<p>I implemented in two stages. Firstly, I generated CSS to render a radio input (with label) as a block with the tick if it is selected, using a bit of custom Safari CSS (<code>-khtml-appearance:none;</code>) to suppress the native style of a checkbox &#8211; which interestingly changes from relatively nice on an iPod Touch v2 to quite nasty on an iPhone 3GS.</p>
<p>Next up, I tried to think of a natural way to express the options in HTML, and ended up with the idea of getting a script to rewrite any select in a form which has the CSS class of &#8216;panel&#8217; into a seperate linked panel.  The markup you write looks like this:</p>
<pre name="code" class="html">&lt;div class="row"&gt;
 &lt;label for="buy_railcard"&gt;Railcard&lt;/label&gt;
 &lt;select id="buy_railcard" name="railcard" class="panel"&gt;
  &lt;option value="rc_none" selected&gt;None&lt;/option&gt;
  &lt;option value="rc_youth"&gt;Youth&lt;/option&gt;
  &lt;option value="rc_family"&gt;Family&lt;/option&gt;
  &lt;option value="rc_senior"&gt;Senior&lt;/option&gt;
  &lt;option value="rc_disabled"&gt;Disabled&lt;/option&gt;
  &lt;option value="rc_network"&gt;Network&lt;/option&gt;
  &lt;option value="rc_hm"&gt;HM Services&lt;/option&gt;
 &lt;/select&gt;
&lt;/div&gt;</pre>
<p>By default, it would render like this (taken from desktop Safari, just to make my life easier):</p>
<p style="text-align: center;"><img title="Selects as they would render in Safari without the script" src="http://masochismtango.com/wp-content/uploads/2009/08/select.jpg" alt="Selects as they would render in Safari without the script" /></p>
<p>The script rewrites the above HTML on the original form like this:</p>
<pre name="code" class="html">&lt;div class="row"&gt;
 &lt;a href="#select__buy_railcard"&gt;Railcard
  &lt;input type="hidden" name="railcard" value="rc_none"/&gt;
  &lt;var id="railcard-rc_none" class="_lookup rc_none"&gt;None&lt;/var&gt;
 &lt;/a&gt;
&lt;/div&gt;</pre>
<p>Points to note:</p>
<ul>
<li>The original form will submit in exactly the same way to the server
<ul>
<li>the new hidden field within the link has the same <code>name</code> as the <code>select</code> and the <code>value</code> of the selected <code>option</code>.</li>
</ul>
</li>
<li>The textual label of the selected option is copied into the var tag, which actually visually shows the selected option to the user
<ul>
<li>the var is also given the option value as a css class, which allows us to do the funky icons on all labels if we want (using <code>:before</code> generated content, in this case).</li>
</ul>
</li>
</ul>
<p>The script also creates a new <code>form.panel</code> screen containing the options as a radio group, like this:</p>
<pre name="code" class="html">&lt;form id="select__buy_railcard" class="panel" title="Railcard"&gt;
 &lt;fieldset class="radiogroup"&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_none" for="buy_railcard_option_rc_none"&gt;None
    &lt;input id="buy_railcard_option_rc_none" type="radio" value="rc_none" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_youth" for="buy_railcard_option_rc_youth"&gt;Youth
    &lt;input id="buy_railcard_option_rc_youth" type="radio" value="rc_youth" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_family" for="buy_railcard_option_rc_family"&gt;Family
    &lt;input id="buy_railcard_option_rc_family" type="radio" value="rc_family" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_senior" for="buy_railcard_option_rc_senior"&gt;Senior
    &lt;input id="buy_railcard_option_rc_senior" type="radio" value="rc_senior" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_disabled" for="buy_railcard_option_rc_disabled"&gt;Disabled
    &lt;input id="buy_railcard_option_rc_disabled" type="radio" value="rc_disabled" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_network" for="buy_railcard_option_rc_network"&gt;Network
    &lt;input id="buy_railcard_option_rc_network" type="radio" value="rc_network" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;div class="row"&gt;
   &lt;label class="rc_hm" for="buy_railcard_option_rc_hm"&gt;HM Services
    &lt;input id="buy_railcard_option_rc_hm" type="radio" value="rc_hm" name="railcard"/&gt;
   &lt;/label&gt;
  &lt;/div&gt;
  &lt;/fieldset&gt;
&lt;/form&gt;</pre>
<h2>Date Selection</h2>
<p>One of the greatest things in HTML 5, to me, is also one of the simplest &#8211; the <a href="http://dev.w3.org/html5/markup/input.html#input">input tag</a> has now been extended to allow all sorts of new types, such as <a href="http://dev.w3.org/html5/markup/input.date.html#input.date">dates</a>, <a href="http://dev.w3.org/html5/markup/input.tel.html#input.tel">telephone numbers</a> and even <a href="http://dev.w3.org/html5/markup/input.color.html#input.color">colours</a>.  The idea is that browsers will then handle these specially with funky calendar drop downs etc that curretly have to be implemented in Javascript &#8211; and on mobiles, they can use a more appropriate native solution optimised for the keypad, with address book access etc.</p>
<p>Obviously, no mobile browser has bothered to implement these &#8211; today you&#8217;ll have to look at Opera desktop if you want to see a browser starting to do it correctly.</p>
<p>In the absence of a native widget, I implemented a date selector myself.  On the iPhone itself, this is implemented in a style similar to HTML drop-downs in Safari &#8211; difficult to achieve in HTML alone, and not actually that natural for selecting travel dates.  I opted to support date selection with a calendar reminiscent of theiPhone calendar app&#8217;s instead:</p>
<p style="text-align: center;"><a href="http://www.flickr.com/photos/masabi/3774343471/"><img title="Date selection with iUI extension" src="http://farm4.static.flickr.com/3503/3774343471_c45bc0c7f9.jpg" alt="Date selection with iUI extension" /></a></p>
<p>As before, this is implemented with a standard piece of HTML that is rewritten by the script.  To add a calendar you add an input like this to your form:</p>
<pre name="code" class="html">&lt;div class="row"&gt;
 &lt;label for="buy_travelDate"&gt;Outbound Date&lt;/label&gt;
 &lt;input name="travelDate" id="buy_travelDate" type="date" class="date" value="1/1/2009"&gt;
&lt;/div&gt;</pre>
<p>The <code>type</code> you choose to assign can be either <code>date</code> or <code>text</code>.  If you set the input&#8217;s <code>type</code> to <code>text</code> by hand, the script will always replace it with a custom calendar.  Currently, Safari rewrites any <code>type</code> it doesn&#8217;t understand to <code>text</code> when the page loads; if and when <code>date</code> support is added, this will presumably cease and the script will stop handling dates for you and leave selection to the browser.  Your choice which you want!</p>
<p>When the form is first loaded, your markujp is rewritten like this:</p>
<pre name="code" class="html">&lt;div class="row"&gt;
 &lt;a href="#select__buy_journeytype"&gt;Journey Type
  &lt;input type="hidden" name="journeytype" value="tt_single"/&gt;
  &lt;var id="journeytype-tt_single" class="_lookup tt_single"&gt;Single&lt;/var&gt;
 &lt;/a&gt;
&lt;/div&gt;</pre>
<p>There will also be a (singleton) extra screen created for the date picker like this:</p>
<pre name="code" class="html">&lt;form id="_datepicker" class="panel"&gt;
 &lt;p&gt;
  &lt;span id="_dpback" class="back"&gt; &lt;/span&gt;
  &lt;span id="_dpmonth" class="month"/&gt;
  &lt;input id="_dphidden" type="hidden"/&gt;
  &lt;span id="_dpfwd" class="fwd"&gt; &lt;/span&gt;
 &lt;/p&gt;
 &lt;table id="_dptable" cellspacing="0" cellpadding="0" border="0"&gt;
  &lt;colgroup&gt;
   &lt;col class="sun"/&gt;
   &lt;col class="mon"/&gt;
   &lt;col class="tue"/&gt;
   &lt;col class="wed"/&gt;
   &lt;col class="thu"/&gt;
   &lt;col class="fri"/&gt;
   &lt;col class="sat"/&gt;
  &lt;/colgroup&gt;
  &lt;thead&gt;
   &lt;tr class="days"&gt;
    &lt;th&gt;Sun&lt;/th&gt;
    &lt;th&gt;Mon&lt;/th&gt;
    &lt;th&gt;Tue&lt;/th&gt;
    &lt;th&gt;Wed&lt;/th&gt;
    &lt;th&gt;Thu&lt;/th&gt;
    &lt;th&gt;Fri&lt;/th&gt;
    &lt;th&gt;Sat&lt;/th&gt;
   &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
   &lt;tr class="wk1"&gt;
    &lt;td&gt; &lt;/td&gt;
    &lt;!-- etc --&gt;
   &lt;/tr&gt;
   &lt;tr class="wk2"&gt;...&lt;/tr&gt;
   &lt;tr class="wk3"&gt;...&lt;/tr&gt;
   &lt;tr class="wk4"&gt;...&lt;/tr&gt;
   &lt;tr class="wk5"&gt;...&lt;/tr&gt;
   &lt;tr class="wk6"&gt;...&lt;/tr&gt;
  &lt;/tbody&gt;
 &lt;/table&gt;
&lt;/form&gt;</pre>
<p>The screen is a singleton, which means it will only be created once, and reused for every date picker in your app (which saves on memory).  Month and day names are taken from arrays which could be externalised and internationalised easily if required (CSS class names on <code>col</code>s are hardcoded though).</p>
<p>Every time the date picker is selected, the script refreshes the calendar, working out the date from the hidden field on the original form and copying over the relevant details.  Table cell contents are updated with the relevant date, and <code>onClick</code> events set to make selections work.</p>
<p>There are a few options, which can be passed in using CSS classes applied to the date field:</p>
<ol>
<li>If the <code>today</code> class is specified, then any selection of today&#8217;s date will be stored on the form (and shown visually) as &#8216;Today&#8217; instead of the date;</li>
<li>If the <code>future</code> class is specified, only today and future dates are visible and selectable;</li>
<li>If the <code>past</code> class is specified, only today and past dates are visible and selectable;</li>
<li>A date pattern can be specified using the following syntax:
<ul>
<li>Parts of the date:
<ul>
<li><code>d</code> for the date eg. 28</li>
<li><code>D</code> for the day&#8217;s abbreviated name eg. Mon or Fri</li>
<li><code>m</code> for the month&#8217;s number eg. 1 (for January)</li>
<li><code>M</code> for the month&#8217;s abbreviated name eg. Jan or Dec</li>
<li><code>y</code> for the year as two digits eg. 09</li>
<li><code>Y</code> for the year as four digits eg. 2009</li>
</ul>
</li>
<li>Other symbols:
<ul>
<li><code>_</code> <i>underscore</i> is converted to a space</li>
<li><code>c</code> is converted to a comma</li>
<li>Hyphens and slashes are also allowed.</li>
</ul>
</li>
<li>Examples:
<ul>
<li><code>d/m/y</code> goes to 3/8/09</li>
<li><code>d-M-y</code> goes to 3-Aug-09</li>
<li><code>Dc_d_M_Y</code> goes to Mon, 3 Aug 2009</li>
</ul>
</li>
</ul>
</li>
</ol>
<p>If a date format is set, then only that format can be used to specify the value of the field.</p>
<p>That&#8217;s basically it for my extensions &#8211; having documented them, I&#8217;ll now see if anyone wants them merged in to the main iUI framework!</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/08/03/mobile-webapps-iui-framework-extensions/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
		<item>
		<title>Multilingual Wordpress Sites</title>
		<link>http://masochismtango.com/2009/06/07/multilingual-wordpress-sites/</link>
		<comments>http://masochismtango.com/2009/06/07/multilingual-wordpress-sites/#comments</comments>
		<pubDate>Sun, 07 Jun 2009 20:09:11 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[multilingual]]></category>
		<category><![CDATA[qTranslate]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=206</guid>
		<description><![CDATA[
Over the last month I&#8217;ve been building a tri-lingual site for an excellent little farm in Tuscany, Corzano e Paterno, where among other things they make prize winning cheeses, olive oil and wines (all of which I recommend trying whilst staying in one of their villas &#8211; really, it&#8217;s great!)
The one requirement that caused me [...]]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/206.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>Over the last month I&#8217;ve been building a tri-lingual site for an excellent little farm in Tuscany, <a href="http://www.corzanoepaterno.com/">Corzano e Paterno</a>, where among other things they make prize winning <a href="http://www.corzanoepaterno.com/cheese">cheeses</a>, <a href="http://corzanoepaterno.com/wine/olive-oil">olive oil</a> and <a href="http://www.corzanoepaterno.com/wine">wines</a> (all of which I recommend trying whilst <a href="http://corzanoepaterno.com/agriturismo">staying in one of their villas</a> &#8211; really, it&#8217;s great!)</p>
<p>The one requirement that caused me the biggest headache was the need to handle three languages.  There are quite a few plugins which can handle two, but only a handful which manage three or more and almost all aren&#8217;t actually that easy to use and/or reliable.  In the end, after much experimentation, I settled on the excellent <a href="http://www.qianqin.de/qtranslate/">qTranslate</a> which really beat all of the others hands down. Highly recommended.</p>
<p>qTranslate integrates nicely into the Wordpress page and post admin UI, offering multiple header fields and extra tabs for each language you want to add:</p>
<p><a class="img" href="http://www.qianqin.de/qtranslate/"><img class="aligncenter size-full wp-image-207" title="qTranslate Wordpress plugin's Admin UI" src="http://masochismtango.com/wp-content/uploads/2009/06/qtranslate.gif" alt="qTranslate Wordpress plugin's Admin UI" width="550" height="394" /></a></p>
<p>For standard functions, everything is great and works seamlessly.  However if you need to directly access the contents of a field in a post/page object, things will look a bit weird:</p>
<pre name="code" class="php">&lt;?php echo($single-&gt;post_title); ?&gt;</pre>
<p>will look on the page like:</p>
<pre>EnglishItalianoDeutsch</pre>
<p>generating the following actual markup:</p>
<pre name="code" class="html">&lt;!--:en--&gt;English&lt;!--:--&gt;&lt;!--:it--&gt;Italiano&lt;!--:--&gt;&lt;!--:de--&gt;Deutsch&lt;!--:--&gt;</pre>
<p>So what to do?  Well, fortunately, we can just hijack a function hat qTranslate uses itself to resolve the current translation for any field &#8211; replace the :</p>
<pre name="code" class="php">&lt;?php echo(qtrans_useCurrentLanguageIfNotFoundUseDefaultLanguage($single-&gt;post_title)); ?&gt;</pre>
<p>It&#8217;s that simple &#8211; wrap that function around any multilingual property you need to read, and qTranslate will do all the heavy lifting for you!</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2009/06/07/multilingual-wordpress-sites/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Portfolio: Temono web site</title>
		<link>http://masochismtango.com/2008/11/15/portfolio-temono-web-site/</link>
		<comments>http://masochismtango.com/2008/11/15/portfolio-temono-web-site/#comments</comments>
		<pubDate>Sat, 15 Nov 2008 16:38:49 +0000</pubDate>
		<dc:creator>Tom Godber</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[portfolio]]></category>
		<category><![CDATA[Powerpoint]]></category>
		<category><![CDATA[Temono]]></category>
		<category><![CDATA[web site]]></category>
		<category><![CDATA[Wordpress]]></category>

		<guid isPermaLink="false">http://masochismtango.com/?p=3</guid>
		<description><![CDATA[
Just completed a rebrand for Telecomms PR outfit Temono, including a Wordpress-based web site, Powerpoint .pot template and related collateral:

The site features a blog, customer case studies and all of the standard static content you would expect.
]]></description>
			<content:encoded><![CDATA[<p><img src='http://masochismtango.com/wp-content/plugins/simple-post-thumbnails/timthumb.php?src=/wp-content/thumbnails/3.jpg&amp;w=48&amp;h=48&amp;zc=1&amp;ft=jpg' alt='post thumbnail' /></p>
<p>Just completed a rebrand for Telecomms PR outfit <a href="http://www.temono.com/">Temono</a>, including a Wordpress-based web site, Powerpoint .pot template and related collateral:</p>
<p style="text-align:center"><a title="The Temono web site" href="http://www.temono.com/" class="img"><img class="size-medium wp-image-9" title="Temono web site" src="http://masochismtango.com/wp-content/uploads/2009/05/temono.jpg" alt="Temono web site - screenshot" width="300" height="241" /></a></p>
<p>The site features a blog, customer case studies and all of the standard static content you would expect.</p>
]]></content:encoded>
			<wfw:commentRss>http://masochismtango.com/2008/11/15/portfolio-temono-web-site/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
