Just a Girl :: Lightroom, Photoshop, Exiftool, PHP, Perl, RSS and XSLT - tips, references and examples.
Firefox User Content CSS
Monday, May 7, 2012
One of my favorite places in the world, The Picture Perfect School of Photography, offers online photography classes. Assignments are uploaded each week for critique by the instructor(s). Students are also encouraged to comment on submissions.
The site has a clean, inviting layout but, in my opinion, displaying assignment photos in the browser could use a little tweak.
Site- or Page-Specific Customizations
In Firefox, you can tweak a website's style using the userContent.css file in your profile. If it exists, the file is located in the chrome directory of your Firefox profile. If it doesn't, simply create it there.
userContent.css
/* alter pages that begin with a specific prefix */@‑moz‑document url‑prefix(http://ppsop.net/member/ViewPhoto.aspx),url‑prefix(http://ppsop.com/member/ViewPhoto.aspx){body, table, td{background-color: rgb(51, 51, 51) !important;color: rgb(204, 204, 204) !important;}table, td{border: 0 !important;}- td
{vertical-align: top !important;}- img
{margin: 2em !important;}}
By giving the rule priority ‑ !important; ‑ we can make sure that these rules override the site's styles. Note: if the website changes their design in the future, it could effect your customizations.
Where to Go for More Information
CSS Reference: Mozilla Extensions ‑ https://developer.mozilla.org/en/CSS/@‑moz‑document
Customizing Mozilla ‑ http://www‑archive.mozilla.org/unix/customizing.html#userContent
Where is my Mozilla profile located? ‑ http://www.gemal.dk/mozilla/profile.html
Firefox Help: Profiles ‑ http://support.mozilla.org/en-US/kb/Profiles
» Filed under: CSS, Firefox, Tips
0 Comments »
Cron and PHP
Wednesday, April 6, 2011
I recently designed a website for a client which inluded access to an MLS Data Feed (IDX/XML)—a service provided for members of the Realtors Association of Maui. One the items on her wish list was an early‑morning email summarizing the previous day's new MLS listings.
Although cron is commonly used to automate system maintenance or administration, you can utilize cron to send an email with information from your site.
Cron
Cron is a scheduling daemon available on Linux and other Unix variants that executes commands at specified dates and times according to instructions in a set of files called "crontabs." A system‑wide crontab file is usually stored in the /etc directory, and each users crontab file in the /var/spool/cron directory.
Creating Your Crontab With Plesk
Most web hosting providers allow access to cron through an easy‑to‑use web interface. Mine is Plesk (version 9.5.4). To add a cron job in Plesk:
- Log in to Plesk.
- Select the domain you wish to manage, and choose Scheduled Tasks from the Additional Tools group.
- Select the system user account and click Schedule New Task.
- Specify when to run your command, and which command to run.
For example, if you want to run your php script daily at 7:00am and have the information (new listings) sent to you via email, specifiy the following in the input boxes:

cron - New Task Details
Note: If you are running PHP‑CGI (not as an Apache module), you may need to suppress HTTP header output by adding the Quiet‑mode switch to the command:
php ‑q httpdocs/newListingsEmail.php
By default, the server will send output (or error messages) from the cron job to the email address you specified when you set up the domain. You can change this email address in 'Preferences.'

cron - New Task
The PHP Script
newListingsEmail.php
<?phprequire_once('ext/inc_funcGen.php');require_once('ext/inc_funcXML.php');require_once('ext/cls_phpmailer-lite.php');require_once('int/inc_constants.php');require_once('int/inc_email.php');require_once('int/inc_ram.php');$yesterday = date('Y-m-d', strtotime('-1 day', APP_NOW));$list = getNew($yesterday);$xml = getSearchXML($list, $method);$listingsHTML = displayEmail($xml, $yesterday);$mail = new PHPMailerLite();$mail->Subject = \'New MLS Listings: \' . $yesterday;$mail->IsHTML(true);$mail->SetFrom(LIST_EMAIL, DISPLAY_NAME);foreach($nladdr as $addr){$mail->AddAddress($addr);$mail->Body = $listingsHTML;$mail->Send();$mail->ClearAddresses();}echo 'Email sent to subscribers of \'New MLS Listings\': ' . DATE_ISO;?>
Potential PHP Gotchas
I have a 'library' of common include files that reside in a directory outside the domains on my server:

Server File System
A list of directories where the require(), include(), fopen(), file(), readfile() and file_get_contents() functions look for files is specified in each domain's vhost.conf file.
vhost.conf
<IfModule mod_php5.c>...php_admin_value open_basedir "/var/www/vhosts/example.com/httpdocs:/var/www/vhosts/library:/tmp"php_value include_path "/var/www/vhosts/library/ext:."...</IfModule>
Interestingly, these directives are ignored when executing a PHP script through cron, and I received the following email message when I tested the cron job:
From: Cron DaemonDate: 21 Mar 2011 07:00:01 -1000To: <username@example.com>Subject: Cron <username@example.com> php httpdocs/newListingsEmail.phpPHP Warning: require_once(inc_funcGen.php): failed to open stream: No such file or directory in /var/www/vhosts/example.com/httpdocs/newListingsEmail.php on line 1PHP Fatal error: require_once(): Failed opening required 'inc_funcGen.php' (include_path='.:') in /var/www/vhosts/example.com/httpdocs/newListingsEmail.php on line 1
Solution: Hard-linking Files
$ ln /var/www/vhosts/library/ext/inc_funcGen.php /var/www/vhosts/example.com/httpdocs/int/inc_funcGen.php$ ln /var/www/vhosts/library/ext/inc_funcXML.php /var/www/vhosts/example.com/httpdocs/int/inc_funcXML.php$ ln /var/www/vhosts/library/ext/cls_phpmailer-lite.php /var/www/vhosts/example.com/httpdocs/int/cls_phpmailer-lite.php
Using find to Confirm Creation of Hard Links
$ cd /var/www/vhosts/example.com/httpdocs/int/$ find . -type f -links +1 -printf "hard-links: %n\tinode: %i\t%p\n"hard-links: 2 inode: 332174984 ./inc_funcGen.phphard-links: 2 inode: 645701270 ./inc_funcXML.phphard-links: 2 inode: 645701236 ./cls_phpmailer-lite.php
Where to Go for More Information
cron, Wikipedia ‑ http://en.wikipedia.org/wiki/cron
Introducing Cron ‑ http://articles.sitepoint.com/print/introducing-cron
Using Crontab with Plesk to call PHP files ‑ http://daipratt.co.uk/crontab-plesk-php/
Cron and Crontab usage and examples ‑ http://www.pantz.org/software/cron/croninfo.html
Description of core php.ini directives ‑ http://php.net/manual/en/ini.core.php
Hard link, Wikipedia ‑ http://en.wikipedia.org/wiki/Hard_link
» Filed under: Cron, Crontab, Plesk, PHP, Email, Tips
0 Comments »
Using Exiftool to Add Keywords to Your RAW Image File
Thursday, November 4, 2010
Lightroom displays the flash EXIF metadata in a field they call Flash. Unhappily, the value of this tag is either 'Did fire' or 'Did not fire.' This tag appears as Metadata criteria in the Library Filter bar as 'Flash States,' but one is not further enlightened by filtering on this criteria.
A simple solution is to write the camera's flash metadata to Lightroom's Keyword tags.

Perl 5 ‑ Things Your Mother Didn't Teach You About Knitting
As part of my workflow, I've been using exiftool's command‑line interface to add exposure compensation keywords to my RAW image files. I made the switch to a Perl script because I couldn't think of a way to use multiple '‑if' conditions–one for exposure compensation, the others for flash compensation (i.e., ‑if '$FlashMode =~ "Fired"')–without making multiple passes.
Flash Photography ‑ Who Knew It Would Be So Complicated?
It seems there are a zillion combinations of built‑in and external flash settings (I haven't even tried flash bracketing), so I've decided to do my 'regular' flash photography as outlined below. I'm using a Nikon D300 camera and Nikon Autofocus Speedlight SB‑800 flash. My Perl script is dependant on these flash settings for appropriate tag retrieval.
- Will not use the built‑in flash on it's own.
- Will use SB‑800 either in the shoe or remotely.
- Will not use command dial for compensation.
- When SB‑800 in shoe, will set e3 to TTL and set compensation on SB‑800.
- When SB‑800 in remote mode, will set e3 to Commander Mode then choose the mode and compensation on the camera.
Note: even if [‑ ‑] is selected for [Built‑in flash] > [Mode], the built‑in flash must be raised so that monitor pre‑flashes will be emitted. The built‑in flash does not fire, but the AF‑assist illuminator lights. The AF‑assist illuminator lights are so bright I thought the built‑in flash was firing, which certainly added to my confusion. I did some research and found this: The Nikon SG‑3IR IR Panel for Camera Built‑In Flashes attaches to the camera's flash shoe, and covers and prevents the camera's built‑in flash (pre‑flash) from influencing the exposure (http://photo.net/nikon-camera-forum/00O8yc).
Tags may differ for each camera make/model. Run this command to output a list of tags to a file (one for each image), in a subfolder named 'tags':exiftool -a -u -u -g1 -H *.nef -w tags/%f_dblu.txt
For an explanation of the ‑a, ‑u, ‑g1, ‑H command‑line options, see the ExifTool Option Summary.
Acknowledgements
Many, many thanks to Phil Harvey for his extraordinary patience in helping me with this—my first ever—Perl script.
The Perl Script
#!/usr/bin/perl -wuse Time::localtime;use File::Copy;use Image::ExifTool;require 'Image/ExifTool/Shift.pl';# camera set to UTC0; will shift [‑]10 for Hawaiimy $tz = 10;my $curyr = localtime‑>year() + 1900;my $cidir = '/Volumes/PHOTOS/CameraImport';my $bdrv='/Volumes/B‑PHOTOS/raw';my $pat = 'nef';my $bdir = "$bdrv/$pat/$curyr";my $ext = '.' . $pat;# this part of the camera's folder name is always the samemy $cpdir = '/Volumes/NIKON D300/DCIM';# the subfolder name is incremented every 10,000 images but will always have 'ND300' as part of it's namemy $ccdir = 'ND300';my $dirpat = $ccdir;opendir(my $cdh, $cpdir) or die "Couldn't open $cpdir : $!";my @cdirs = grep(/$dirpat/i, readdir($cdh));closedir($cdh);foreach $cdir(@cdirs) {if(‑d "$cpdir/$cdir") {$imgdir = "$cpdir/$cdir";last;}}if(!defined($imgdir)) {die "Couldn't open $cpdir";}opendir($dh, $imgdir) or die "Couldn't open $imgdir : $!";# fill an array of all image files in the current directory# note case‑insensitive pattern match (/i)@imgfiles = grep(/$pat/i, readdir($dh));closedir($dh);# process all the image files in our arrayforeach $imgfile(@imgfiles) {$exifTool = new Image::ExifTool;# extract meta information from the image;# note use of full path in filename$exifTool‑>ExtractInfo("$imgdir/$imgfile");# get date and time$exifdto = $exifTool‑>GetValue('DateTimeOriginal');# set GPS date and time stamps$exifTool‑>SetNewValue('GPSDateStamp' => $exifdto);$exifTool‑>SetNewValue('GPSTimeStamp' => $exifdto);# camera set to UTC0, so shift the dates to reflect proper locale# for convenience, a shortcut tag called AllDates has been defined to represent these three tags: DateTimeOriginal, CreateDate and ModifyDate# http://www.sno.phy.queensu.ca/~phil/exiftool/#shift$exifTool‑>SetNewValue('AllDates' => $tz, 'Shift' => ‑1);# get flash compensation tags$fm = $exifTool‑>GetValue('FlashMode');# if the flash fired...if($fm =~ /^Fired/) {# get the mode$fcm = $exifTool‑>GetValue('FlashCommanderMode');# if mode is Offif($fcm eq 'Off') {$tag = 'ExternalFlashExposureComp';}# else mode is Onelse {$tag = 'FlashGroupACompensation';$flashgrp = ': Group A';}$val = $exifTool‑>GetValue($tag);# want decimal values here, convert fractions$num = Image::ExifTool::Exif::ConvertFraction($val);# round to one decimal place (i.e., '0.666666666666667' after fraction conversion)$val = sprintf('%.1g', $num);$fec = 'FEC: ' . $val;$tn = 'Flash Exposure Compensation';# $flashgrp is always defined after it's first occurrence in the loop...if(defined($flashgrp)) {$tn .= $flashgrp;# ...so undefine the bleeping thing if you found it earlierundef $flashgrp;}# add flash compensation keywords without replacing existing keywords in the file$exifTool‑>SetNewValue('IPTC:Keywords' => [$tn, $fec], 'AddValue' => 1);}# get exposure compensation tags$sm = $exifTool‑>GetValue('ShootingMode');if($sm =~ /Exposure Bracketing/) {$tn = 'Bracketing Exposure Compensation';$val = $exifTool‑>GetValue('ExposureBracketValue');$num = Image::ExifTool::Exif::ConvertFraction($val);$valEBV = sprintf('%.1g', $num);$ebv = 'EBV: ' . $valEBV;$val = $exifTool‑>GetValue('ExposureCompensation');$num = Image::ExifTool::Exif::ConvertFraction($val);$valEC = sprintf('%.1g', $num);# calculate 'actual' exposure compensation$num = $valEC ‑ $valEBV;$val = sprintf('%.1g', $num);$ec = 'EC: ' . $val;# add exposure compensation keywords without replacing existing keywords in the file$exifTool‑>SetNewValue('IPTC:Keywords' => [$tn, $ebv, $ec], 'AddValue' => 1);}# get values for use in new filename# set desired date format$exifTool‑>Options('DateFormat' => '%Y%m%d‑%H%M%S');# to *use* a shifted date/time value the shift must be applied to the unformatted (ValueConv) value# then you must do the DateFormat conversion yourself$dto = $exifTool‑>GetValue('DateTimeOriginal', 'ValueConv');Image::ExifTool::ShiftTime($dto, $tz, ‑1);$dto = $exifTool‑>ConvertDateTime($dto);# Nikon records up to 6 frames per second; get 'SubSecTimeOriginal' to avoid filename collision$ssto = $exifTool‑>GetValue('SubSecTimeOriginal');# get shutter count$sc = $exifTool‑>GetValue('ShutterCount');# pad the shutter count number$sc = '_' . sprintf('%07d', $sc);# will occasionally grab Karl or Nicole's Nikon, so get serial number$sn = '_' . $exifTool‑>GetValue('SerialNumber');# concatenate all the pieces of the new filename# i.e., 20100126‑08040600_0020213.nef$nn = $dto . $ssto . $sc . $sn . $ext;# add information to source file, writing output to new file# http://owl.phy.queensu.ca/~phil/exiftool/ExifTool.html#WriteInfo# note use of full path in filenames$exifTool‑>WriteInfo("$imgdir/$imgfile", "$cidir/$nn");# copy the file to backup drivecopy("$cidir/$nn", "$bdir/$nn");}
How it Works
The Image::ExifTool library provides an extensible set of Perl modules to read and write meta information, accessed through the methods of the public interface (GetValue, SetNewValue, etc.). Note: some Image::ExifTool methods and modules should not be accessed directly because their interface may change with future versions (i.e., Image::ExifTool::Exif::ConvertFraction, Image::ExifTool::ShiftTime, etc.).
Where to Go for More Information
ExifTool Forum ‑ http://u88.n24.queensu.ca/exiftool/forum/
The Image::ExifTool Perl Library Module ‑ http://owl.phy.queensu.ca/~phil/exiftool/ExifTool.html
ExifTool ‑ http://www.sno.phy.queensu.ca/~phil/exiftool/
Nikon Tags ‑ http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/Nikon.html
» Filed under: ExifTool, Keywords, Lightroom, Metadata Filtering, Perl, Nikon D300, Nikon SB-800, Tips
0 Comments »
Use XSLT to Transform Your RSS Feed into a Sitemap
Tuesday, October 19, 2010
XSLT is a language for transforming XML documents into other XML documents. A Sitemap is an XML file that lists the URLs for a site, so we can use XSLT to transform an RSS document into an XML Sitemap.
In this article I'll describe how to use PHP to transform your RSS document into an XML Sitemap and specify it's location in your robots.txt file for 'Autodiscovery.'
The Feed
feed.xml
<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><description>Characterization or summary of the feed</description><link>http://blog.example.com/</link><title>Title of the Weblog</title><item><title>Title of the Item</title><link>http://blog.example.com/?id=i_20101019124542-1000</link><pubDate>Tue, 19 Oct 2010 12:45:42 -1000</pubDate><category><![CDATA[Photoshop]]></category><category><![CDATA[Tips]]></category><description><![CDATA[A summary of the item"s content.]]></description><content:encoded><![CDATA[<p>Define the full content of the item, suitable for presentation as XHTML.</p>]]></content:encoded></item></channel></rss>
The XSL Stylesheet
sitemap.xslt
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:php="http://php.net/xsl" exclude-result-prefixes="php"><xsl:output method="xml" omit-xml-declaration="no" indent="no" encoding="utf-8" /><xsl:template match="/"><urlset><xsl:apply-templates select="//item" /></urlset></xsl:template><xsl:template match="item"><url><xsl:apply-templates select="link" /><xsl:apply-templates select="pubDate" /><changefreq>never</changefreq></url></xsl:template><xsl:template match="link"><loc><xsl:value-of select="." /></loc></xsl:template><xsl:template match="pubDate"><lastmod><xsl:if test="function-available('php:function')"><xsl:variable name="dt" select="php:function('strtotime', string(.))" /><xsl:value-of select="php:function('date', 'c', number($dt))"/></xsl:if></lastmod></xsl:template></xsl:stylesheet>
The Transformation
sitemap.php
<?phpfunction transform_XML($docXSL, $docXML){$xsl = new DOMDocument;$xsl->load($docXSL);$xml = new DOMDocument;$xml->load($docXML);$xml->loadXML($docXML);$xp = new XSLTProcessor();$xp->registerPhpFunctions();$xp->importStylesheet($xsl);return $xp->transformToXML($xml);}$xml = transform_XML('sitemap.xslt', 'feed.xml');$sitemap = new DomDocument;$sitemap->formatOutput = true;$sitemap->loadXML($xml);$el = $sitemap->getElementsByTagName('urlset')->item(0);$el->setAttribute('xmlns', 'http://www.sitemaps.org/schemas/sitemap/0.9');$el->setAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');$el->setAttribute('xsi:schemaLocation', 'http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd');$sitemap->save('sitemap.xml');?>
The Sitemap
sitemap.xml
<?xml version="1.0" encoding="utf-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd"><url><loc>http://blog.example.com/</loc><lastmod>2010-10-19T12:45:42-10:00</lastmod><changefreq>weekly</changefreq></url><url><loc>http://blog.example.com/?id=i_20101019124542-1000</loc><lastmod>2010-10-19T12:45:42-10:00</lastmod><changefreq>never</changefreq></url></urlset>
If you have more than 50,000 URLs, you will have to create multiple Sitemap files. You can submit these separately or list them in a Sitemap Index file (both are equally effective).
Specifying the Sitemap Location in Your robots.txt File
The sitemap location should be the complete URL to the Sitemap, such as: http://blog.example.com/sitemap.xml. This directive is independent of the user‑agent line, so it doesn't matter where it is placed in the file.
robots.txt
Sitemap: http://blog.example.com/sitemap.xmlUser-agent: *Disallow: /
Where to Go for More Information
Sitemaps XML Format ‑ http://www.sitemaps.org/protocol.php
The Web Robots Pages ‑ http://www.robotstxt.org/
W3C Validator for XML Schema ‑ http://www.w3.org/2001/03/webdata/xsv
Google Sitemap Validation Tool ‑ http://www.webmasterwebtools.com/sitemap-validation/
» Filed under: XSLT, RSS, Sitemaps, Sitemap Autodiscovery, Tips
0 Comments »
Use XSLT to Transform Your RSS Feed into XHTML
Monday, October 18, 2010
XSLT is a language for transforming XML documents into other XML documents. RSS documents (feed, web feed, or channel) and XHTML documents (web pages) are just XML documents, so we can use XSLT to transform an RSS document into a styled XHTML web page, and display it in a browser.
There are two approaches to applying XSLT to your XML content. You can perform these XSLT transformations server‑side on your web server, and serve up the resulting XHTML web page to a browser. The other is to use XSLT is to send a source XML document and an XSLT stylesheet to a browser, and ask the browser to apply the XSLT transformation for you.
Given the inconsistencies between [X]HTML support in the different browsers, it should come as no surprise that they offer varying support for XSLT as well. In this article I"ll describe how to use PHP to perform a server‑side transformation of your RSS document into XHTML.
The Feed
Note: The content:encoded element can be used in conjunction with the description element to provide an item's full content along with a shorter summary. While either the description or the content:encoded elements may contain the full content of an item, the description element is more widely supported than the content:encoded element in the context of an RSS 2.0 feed.
feed.xml
<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><description>Characterization or summary of the feed</description><link>http://blog.example.com/</link><title>Title of the Weblog</title><item><title>Title of the Item</title><link>http://blog.example.com/?id=i_20101006075701</link><pubDate>Sun, 17 Oct 2010 08:00:00 -1000</pubDate><category><![CDATA[Photoshop]]></category><category><![CDATA[Tips]]></category><description><![CDATA[A summary of the item"s content.]]></description><content:encoded><![CDATA[<p>Define the full content of the item, suitable for presentation as XHTML.</p>]]></content:encoded></item></channel></rss>
The XHTML Page
index.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"><html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"><head><title>The Title of Your Weblog</title><meta http-equiv="content-type" content="text/html; charset=utf-8" /><meta http-equiv="Content-Language" content="en-us" /><link charset="utf-8" rel="alternate" type="application/rss+xml" title="The Title of Your Weblog" href="http://blog.example.com/feed.xml" /></head><body><div id="content"><div id="content-main"><?php$params = array('max' => POST_MAX);$html = transform_XML(FEED_XSL, FEED_XML, $params);echo($html);?></div><div id="sidebar"><h2>Current</h2><?php$params = array('max' => POST_MAX);$html = transform_XML(CURRENT_XSL, FEED_XML, $params);echo($html);?><h2>Tags</h2><?php$html = transform_XML(TAGS_XSL, FEED_XML);echo($html);?></div></div></body></html>
The XSL Stylesheets
Feed Stylesheet
The feed's content:encoded element must be suitable for presentation as [X]HTML, so no markup is applied in the stylesheet.
feed.xslt
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform"xmlns:content="http://purl.org/rss/1.0/modules/content/"exclude-result-prefixes="content"><xsl:param name="max">5</xsl:param><xsl:output method="xml" omit-xml-declaration="yes" indent="no"/><xsl:template match="/"><xsl:apply-templates select="//item/content:encoded"/></xsl:template><xsl:template match="content:encoded"><xsl:if test="position() <= $max"><xsl:value-of disable-output-escaping="yes" select="."/></xsl:if></xsl:template></xsl:stylesheet>
Current Stylesheet
Generate an [X]HTML list—including hyperlinks—to display the feed's title elements.
current.xslt
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:param name="max">5</xsl:param><xsl:output method="xml" omit-xml-declaration="yes" indent="no"/><xsl:template match="/"><ul class="current"><xsl:apply-templates select="//item[position() <= $max]/title"/></ul></xsl:template><xsl:template match="title"><li><xsl:text disable-output-escaping="yes">» </xsl:text><a href="{../guid}" title="{.}"><xsl:value-of disable-output-escaping="yes" select='.' /></a></li></xsl:template></xsl:stylesheet>
Tags Stylesheet
Generate an [X]HTML list—including hyperlinks and item count—to display the feed's category elements.
Grouping and Sorting: the Muench Method
For this stylesheet, we'll look at using <xsl:key> to group items in an XML document. Commonly referred to as the Muench method—after Steve Muench—this approach has three steps:
- Define a key for the property we want to use for grouping.
- Select all of the category nodes, sorted alphabetically.
- Do some magic with the generate‑id() and key() functions to find unique values. Output is generated only if we are processing the first node returned by the key that matches the value of the current node.
tags.xslt
<?xml version="1.0" encoding="utf-8"?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:output method="xml" omit-xml-declaration="yes" indent="no" /><xsl:key name="catname" match="category" use="." /><xsl:template match="/"><ul class="taglist"><xsl:apply-templates select="//category"><xsl:sort select="." /></xsl:apply-templates></ul></xsl:template><xsl:template match="category"><xsl:if test="generate-id()= generate-id(key('catname', .)[1])"><li><xsl:text disable-output-escaping="yes">» </xsl:text><a href="?cat={.}" title="{.}"><xsl:value-of disable-output-escaping="yes" select="." /> (<xsl:value-of select="count(key('catname', .))"/>)</a></li></xsl:if></xsl:template></xsl:stylesheet>
Where to Go for More Information
RSS 2.0 Specification ‑ http://cyber.law.harvard.edu/rss/rss.html
RSS Best Practices Profile ‑ http://www.rssboard.org/rss-profile
The DOMDocument class ‑ http://php.net/manual/en/class.domdocument.php
The DOMXPath class ‑ http://www.php.net/manual/en/class.domxpath.php
The XSLTProcessor class ‑ http://php.net/manual/en/class.xsltprocessor.php
W3C Feed Validation Service ‑ http://validator.w3.org/feed/
W3C Markup Validation Service ‑ http://validator.w3.org/
W3C CSS Validation Service ‑ http://jigsaw.w3.org/css-validator/
Building Modular XHTML Web Pages with PHP ‑ http://loadaveragezero.com/vnav/labs/PHP/
» Filed under: XSLT, RSS, XHTML, PHP, Tips

