29th July 2004, early evening | Comments (44)
Part two of the redesign explanations (or ‘Giving Away All My Secrets’, as I’m starting to think of it) is going to look at how I present images, code samples, and conversations on this site.
You may have noticed that although I’ve added some drop shadows to this site, I’ve also removed a few, namely the ones that used to adorn the in-post images. I killed them off because a) I thought I’d get out while the going was good, and b) the site looked like shadow-overkill with them in place. That aside, the reason I mention their absence is that it was the effort that went into removing them that inspired me to do what I describe here.
I’m a bit of a tidy-freak sometimes, I get all funny about dealing with code I don’t like, and will go through a tremendous amount of effort to optimise things, when any normal person would just shrug and work around the problem. Such was the case when, having decided I didn’t like the CSS class names I applied to images and their divs, I revisited every post I’ve ever made and altered the code to better fit in with my new way of thinking. Anal-o-rama, I know, but that’s me.
After checking all 166 entries, four things occurred to me:
Nothing motivates my brain like the opportunity to avoid future work, and so I came up with the idea outlined below.
To ensure I never had to go through this painfully boring process again I decided I’d replace all my image tags (and their accompanying divs and classes) with a single XML tag of my own devising: <imageins />
. This tag would contain all the information necessary to fetch and display an image, and would be read and parsed as the page loaded. The PHP scripts that parsed <imageins />
would assemble whatever markup I was currently favouring, and insert it into the document in place of this home-made tag. In this way future rethinks of the code I used to present images would require the alteration of only one PHP file, not many hundreds of blog entries.
I also realised that this technique had the added benefit of removing the dimensions and file type of the images from the database entry, and letting PHP do the work of figuring that out. This meant that should a future redesign require smaller images than I currently use, or should I decide that GIFs are no longer acceptable and wish to convert to PNGs, it’d just be a case of running a Photoshop (or Imagemagick) batch script against my original, large images, and popping the newly created files onto the server in place of the old ones. These changes would be seamless as no file types or dimensions would ever be specified in the blog post entires, so there’d be nothing to break; no mismatch of information, so to speak. Now I could really go to town with CSS in the future, without having to worry about how my post content would be affected — it seemed like another step closer to the ideal of a truly visually-flexible site.
A final upside to this idea was that should an image be missing on page-load I could bypass the browser’s “image not found” error-handling and determine what action to take myself: present a “whoops, I can’t find that picture” message; output nothing at all; email me an alert, etc, etc. Similar error catching could be used for images that linked to more detailed, larger images: if those larger images couldn’t be found then there was no point in printing the link. In this way the number of user-perceived errors could be reduced, and I could be alerted should I ever forget to upload an image file.
Things were looking good.
It then occurred to me that there must be other elements in my blog entries whose markup I might want to change over time; elements (such as conversations, or code examples) for which no standardised set of markup existed. It would be a bit daft to go to all the trouble of fixing my images if, with every redesign, I still had to trawl through my blog posts to restructure problematic code.
My solutions to the problems of image, code sample, and conversation replacements are laid out below. If you like what you see and want to write a Word Press or Movable Type plugin, then please feel free, just ensure you give the appropriate attribution.
As always I’m happy to hear of better ways to do what I've done (I don’t claim to be a PHP-genius), so please feel free to post improvements if you’re a cleverer bugger than me.
Here’s the imageTransform()
function:
<?php
// transform my little image tags
function imageTransform($file, $alt, $caption, $class, $link, $linktitle)
{
// set vars
$html = '';
$container_open = '';
$container_close = '';
$link_start = '';
$link_end = '';
$filetypes = array('jpg', 'gif', 'png');
// get filetype for image
foreach ($filetypes as $type)
{
if (file_exists('/home/d/www/blog/images/'.$file.'.'.$type))
{
$ext = '.'.$type;
break;
}
}
// if no filetype matched, then quit
if (!isset($ext))
{
// insert error handling here
// email yourself an error or whatever you want
return;
}
// get file info
$file_info = getimagesize('/home/d/www/blog/images/'.$file.$ext);
// set vars
$add_width = ($caption <> '') ? 16 : 12;
$width = ' style="width: '.($file_info[0] + $add_width).'px;"';
$class = ($class == '') ? '' : $class.' ';
// if there's a caption
if ($caption <> '')
{
$container_open .= '<div class="'.$class.'caption"'.$width.'>'."\n";
$caption = $caption."\n";
$container_close .= '</div>'."\n";
$width = '';
$class = '';
}
// if there's a link
if ($link <> '')
{
// if it's a link to a larger version of the same image
if ($link == 'large')
{
// get filetype for the larger image
foreach ($filetypes as $type)
{
if (file_exists('/home/d/www/blog/images/large/'.$file.'.'.$type))
{
$ext2 = '.'.$type;
break;
}
}
// if a filetype was found, set the link
if (isset($ext2))
{
$link_start = '<a href="/blog/images/large/'.$file.$ext2.'" title="View a larger version of this image">';
$link_end = '</a>';
}
else
{
// insert error handling here
// email yourself an error or whatever you want
}
}
// else set the link to the url specified
else
{
$link_start = '<a href="'.$link.'" title="'.$linktitle.'">';
$link_end = '</a>';
}
}
// build the html
$html .= $container_open;
$html .= '<div class="'.$class.'image"'.$width.'>';
$html .= $link_start.'<img src="/blog/images/'.$file.$ext.'" '.$file_info[3].' alt="'.$alt.'" />'.$link_end;
$html .= '</div>'."\n";
$html .= $caption;
$html .= $container_close;
// send the html back
return $html;
}
?>
And here are nine examples of how to use it:
// examples of image insertions using my little <imageins /> tag
// example 1a: plain image (displayed block left)
<imageins="456a" alt="A cartoon of a duck" caption="" class="" link="" linktitle="" />
// would produce:
<div class="image" style="width: 216px;"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
// example 1b: decorative image (displayed floated right)
<imageins="456a" alt="A cartoon of a duck" caption="" class="decor" link="" linktitle="" />
// would produce:
<div class="decor image" style="width: 216px;"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
// example 1c: gallery image (displayed floated left)
<imageins="456a" alt="A cartoon of a duck" caption="" class="gallery" link="" linktitle="" />
// would produce:
<div class="image gallery" style="width: 216px;"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
- - - - - - - - - - - - - - - - - - - - -
// example 2a: plain image with caption
<imageins="456a" alt="A cartoon of a duck" caption="A cartoon I drew" class="" link="" linktitle="" />
// would produce:
<div class="caption" style="width: 216px;">
<div class="image"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
A cartoon I drew
</div>
// example 2b: plain image with caption that includes HTML
<imageins="456a" alt="A cartoon of a duck" caption="A cartoon I drew of a <a href='http://www.ducks.org/' title='Go see a site about ducks'>duck</a>" class="" link="" linktitle="" />
// would produce:
<div class="caption" style="width: 216px;">
<div class="image"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
A cartoon I drew of a <a href='http://www.ducks.org/' title='Go see a site about ducks'>duck</a>
</div>
// example 2c: decorative image with caption
<imageins="456a" alt="A cartoon of a duck" caption="A cartoon I drew" class="decor" link="" linktitle="" />
// would produce:
<div class="caption decor" style="width: 216px;">
<div class="image"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
A cartoon I drew
</div>
// example 2d: gallery image with caption
<imageins="456a" alt="A cartoon of a duck" caption="A cartoon I drew" class="gallery" link="" linktitle="" />
// would produce:
<div class="caption gallery" style="width: 216px;">
<div class="image"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></div>
A cartoon I drew
</div>
- - - - - - - - - - - - - - - - - - - - -
// example 3a: plain image with link to larger image of same file name (although the filetype could be different. In this case the matching image turns out to be a JPEG)
<imageins="456a" alt="A cartoon of a duck" caption="" class="" link="large" linktitle="" />
// would produce:
<div class="image" style="width: 216px;"><a href="/blog/images/large/456a.jpg" title="View a larger version of this image"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></a></div>
- - - - - - - - - - - - - - - - - - - - -
// example 3b: plain image with link to some other URL and link text (this text is optional)
<imageins="456a" alt="A cartoon of a duck" caption="" class="" link="http://sidesh0w.com/" linktitle="Ethan smells like a duck. Go see his site" />
// would produce:
<div class="image" style="width: 216px;"><a href="http://sidesh0w.com/" title="Ethan smells like a duck. Go see his site"><img src="/blog/images/456a.gif" width="200" height="186" alt="A cartoon of a duck" /></a></div>
Here’s the result of example 3a:
I’ve always found code presentation to be a pain in the arse. There are just so many things you have to do to prepare code samples for insertion into a blog post, that it often takes the fun out of writing the post in the first place (look at the amount of code on this page, would you fancy prepping all of that?). And if you decide to skip all the preparation and go for <pre>
tags instead, you have to work out what to do when long, non-breaking lines of code mess with your beautiful layout.
The transformation function outlined below hopefully gets around most of the problems you normally run into. It removes the hard work of preparing examples by asking that you simply place them in a text file, as is (or, with a bit of work, you could point the function directly at your live files, for an always up-to-date sample of your code). It removes the need to insert multiple instances of
by using CSS to simulate tab characters. It removes the confusion that can arise when looking at long sections of code, by using Simon Willison’s idea of structuring the whole thing as an ordered list, thus providing line numbers (and some form of meaningful structure). It also highlights comments, so users can easily separate them from the code itself. And finally it provides a link to the original, unmolested text file, should users wish to download the code for their own use.
And how is all that achieved? Well, take a look for yourself:
<?
// this function is used to find the last occurance of a string within another string
// it's used because strrpos() only looks for a single character, not a string
function strLastPos($haystack, $needle)
{
// flip both strings around and search, then adjust position based on string lengths
return strlen($haystack) - strlen($needle) - strpos(strrev($haystack), strrev($needle));
}
// transform my little code tags
function codeTransform($filename)
{
$filename = 'code/'.$filename.'.txt';
// set vars
$list = '';
$cmnt = '';
$multi_line_cmnt = 0;
$tab = '';
$class = '';
// open the file
$file = fopen($filename, 'r');
// for each line in the file
while (!feof($file))
{
// get line
$line = fgets($file, 4096);
// convert tags to safe entities for display
$line = htmlspecialchars($line);
// count the number of tabs at the start of the line, and set the appropriate tab class
$tab = substr_count($line, "\t");
$tab = ($tab > 0) ? 'tab'.$tab : '';
// remove any tabs at the start of the line
$line = str_replace("\t", '', $line);
// find position of comment characters
$slashslash_pos = strpos($line, '//');
$apos_pos = strpos($line, "'");
$slashstar_pos = strpos($line, '/*');
$starslash_pos = strLastPos($line, '*/');
// if it's an ongoing multi-line comment
if ($multi_line_cmnt == 1)
{
$cmnt = 'cmnt';
$multi_line_cmnt = 1;
}
// if it's not an ongoing multi-line comment
if ($multi_line_cmnt <> 1)
{
// if it's a single line comment
if (($slashslash_pos === 0) || ($apos_pos === 0))
{
$cmnt = 'cmnt';
$multi_line_cmnt = 0;
}
else
{
$cmnt = '';
$multi_line_cmnt = 0;
}
// if it's potentially the start of a multi-line comment
if ($slashstar_pos === 0)
{
$cmnt = 'cmnt';
// if multi-line comment end string is found on the same line
if ($starslash_pos == (strlen($line) - 3))
{
$multi_line_cmnt = 0;
}
// if the multi-line comment end string is not found on the same line
else
{
$multi_line_cmnt = 1;
}
}
}
// if the line contains the multi-line end string
if ($starslash_pos == (strlen($line) - 3))
{
$cmnt = 'cmnt';
$multi_line_cmnt = 0;
}
// if both cmnt and tab classes are to be applied
if ( ($cmnt <> '') && ($tab <> '') )
{
$class = ' class="'.$tab.' '.$cmnt.'"';
}
// if only one class is to be applied
else if ( ($cmnt <> "") || ($tab <> "") )
{
$class = ' class="'.$tab.$cmnt.'"';
}
// if no classes are to be applied
else
{
$class = '';
}
// remove return at the end of the line
$line = str_replace("\n", "", $line);
// if the line is blank, put a space in to stop some browsers collapsing the line
if ($line == '')
{
// insert all the information and close the list item
$list .= '<li'.$class.'> </li>'."\n";
}
// otherwise inserts the line contents
else
{
// insert all the information and close the list item
$list .= '<li'.$class.'><code>'.$line.'</code></li>'."\n";
}
}
// close the finle handle
fclose($file);
// add in the link to the file
$list .= '<li class="source">Download this code: <a href="/blog/'.$filename.'" title="Download the above code as a text file">/'.$filename.'</a></li>';
// build the list
$list = '<ol class="code">'."\n".$list."\n".'</ol>'."\n";
// return the list
return $list;
}
?>
Here’s the accompanying CSS:
/* CSS for styling code examples */
ol.code {
border: 1px solid #eee;
color: #333;
font: normal 98%/110% Courier, monospace;
margin: 0;
overflow: auto;
padding: 5px 5px 5px 45px;
}
ol.code li {
background-color: #f4f8fb;
margin: 0;
padding: 0 5px;
}
ol.code li.source {
background-color: #fff;
list-style-type: none;
padding: 5px;
text-align: center;
}
ol.code li+li {
margin-top: 2px;
}
ol.code li.tab1 {padding-left: 15px;}
ol.code li.tab2 {padding-left: 30px;}
ol.code li.tab3 {padding-left: 45px;}
ol.code li.tab4 {padding-left: 60px;}
ol.code li.tab5 {padding-left: 75px;}
ol.code li.tab6 {padding-left: 90px;}
ol.code li code {
color: #333;
}
ol.code li.cmnt code {
color: #824c88;
}
Here’s an example of how to use it all:
// to insert a section of code
<codeins="456d" />
And here’s a screenshot of the output (showing a nonsense function I just made up) as the user will see it :
And finally here’s an example of the XHTML output for the above example (jeez, this is getting loopy):
<ol class="code">
<li><?php</li>
<li class="cmnt">// a made up function to demonstate the various aspects of the codeTransform() function</li>
<li></li>
<li>fooBar($a, $b)</li>
<li class="tab1">{</li>
<li class="tab1 cmnt">// here's a comment</li>
<li class="tab1">if ($a == $b)</li>
<li class="tab2">{</li>
<li class="tab2">print 'toodle-whoopsey';</li>
<li class="tab2">}</li>
<li class="tab1 cmnt">/* and here's a multi-line comment</li>
<li class="tab1 cmnt">it goes on...</li>
<li class="tab1 cmnt">and on...</li>
<li class="tab1 cmnt">and then ends */</li>
<li class="tab1">else</li>
<li class="tab2">{</li>
<li class="tab2">if ($b == 10)</li>
<li class="tab3">{</li>
<li class="tab3 cmnt">// now we're three tabs in</li>
<li class="tab3">print "RAAAAAA! I'M A LION!";</li>
<li class="tab3">}</li>
<li class="tab2">}</li>
<li class="tab1">}</li>
<li>?></li>
<li class="source">Download this code: <a href="/blog/code/456.txt" title="Download the above code as a text file">/code/456.txt</a></li>
</ol>
Actually I haven’t got around to doing this yet, but here’s an example of how I might markup a conversation in XML:
<?xml version="1.0" encoding="utf-8"?>
<scene>
<person value="Marie">
<event type="speech">What does that look like, Grandma?</event>
</person>
<person value="Grandma">
<event type="thought">Hmm, it looks familiar… ah, I know!</event>
<event type="speech">It’s a clitoris!</event>
</person>
<person value="Everyone">
<event type="action">Silence</event>
</person>
<person value="Grandma">
<event type="speech">Oh no! I got muddled up, I mean a chrysalis!</event>
</person>
<person value="Everyone">
<event type="speech">Grandma!</event>
</person>
</scene>
(Some of that flies in the face of the advice that Tantek kindly gave me, but I’m still trying to decide which is better: to store human information in the <person>
tag (as Herr Tantek advises) and have to use an extra tag to enclose each person’s contribution-block; or to store the info in an attribute of <person>
(as I’m currently doing) and save using the extra tag. What do you think?)
Here’s an example of the parsed output, marked-up as I currently present conversations (at the time of writing):
<dl class="dialogue">
<dt>Marie</dt>
<dd><q>What does that look like, Grandma?</q></dd>
<dt>Grandma</dt>
<dd>*Hmm, it looks familiar&&8230; ah, I know!*</dd>
<dd><q>It’s a clitoris!</q></dd>
<dt>Everyone</dt>
<dd>*Silence*</dd>
<dt>Grandma</dt>
<dd><q>Oh no! I got muddled up, I mean a chrysalis!</q></dd>
<dt>Everyone</dt>
<dd><q>Grandma!</q></dd>
</dl>
(I forgot to add this bit in, so thanks to Charles Roper for reminding me that I hadn’t quite finished the article.)
So, you’ve got the transformation functions, and you understand how to replace your code and images with the two tags I made up (<codeins />
, <imageins />
) but how exactly do you get PHP to run the transformation functions? How do you get it to replace those XML tags with actual code examples and images? It’s easy, you do it like this:
<?php
// include the file with the transformation scripts in
include('blog/include/general_transform.php');
// grab your post_body from the db, I do it like this:
// $post_body = stripslashes($row['post_body']);
// transform all the code listings
$post_body = preg_replace('!<codeins="(.*?)" />!ie', "codeTransform('$1')", $post_body);
// transform all the image tags
$post_body = preg_replace('!<imageins="(.*?)" alt="(.*?)" caption="(.*?)" class="(.*?)" link="(.*?)" linktitle="(.*?)" />!ie', "imageTransform('$1', '$2', '$3', '$4', '$5', '$6')", $post_body);
// $post_body now contains your complete, transformed post, ready to be printed out:
print $post_body;
?>
(Thanks to Matt Mullenweg for showing me how to do that.)
And that’s it. Bloomin’ nora, that was a long post, eh?
Right, time for dinner…
Jump up to the start of the post ↑
A collection of miscellaneous links that don't merit a main blog posting, but which are interesting none-the-less.
Our enemies are innovative and resourceful, and so are we. They never stop thinking about new ways to harm our country and our people, and neither do we.— George W Bush (9)
Stuff from the intersection of design, culture and technology.(3)
A selection of blogs I read on a regular basis.