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: , ,

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:

  1. Log in to Plesk.
  2. Select the domain you wish to manage, and choose Scheduled Tasks from the Additional Tools group.
  3. Select the system user account and click Schedule New Task.
  4. 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

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

cron - New Task

The PHP Script

newListingsEmail.php

  • <?php
  • require_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:

library

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 Daemon
  • Date: 21 Mar 2011 07:00:01 -1000
  • To: <username@example.com>
  • Subject: Cron <username@example.com> php httpdocs/newListingsEmail.php
  • PHP 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 1
  • PHP 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.php
  • hard-links: 2  inode: 645701270  ./inc_funcXML.php
  • hard-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: , , , , ,

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.

Lightroom Keywords

Lightroom Keywords

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 -w
  • use Time::localtime;
  • use File::Copy;
  • use Image::ExifTool;
  • require 'Image/ExifTool/Shift.pl';
  • # camera set to UTC0; will shift [‑]10 for Hawaii
  • my $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 same
  • my $cpdir = '/Volumes/NIKON D300/DCIM';
  • # the subfolder name is incremented every 10,000 images but will always have 'ND300' as part of it's name
  • my $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 array
  • foreach $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 Off
  • if($fcm eq 'Off') {
  • $tag = 'ExternalFlashExposureComp';
  • }
  • # else mode is On
  • else {
  • $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 earlier
  • undef $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 drive
  • copy("$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: , , , , , , ,

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

  • <?php
  • function 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.xml
  • User-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: , , , ,

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() &lt;= $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() &lt;= $max]/title"/>
  • </ul>
  • </xsl:template>
  • <xsl:template match="title">
  • <li>
  • <xsl:text disable-output-escaping="yes">&#187; </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:

  1. Define a key for the property we want to use for grouping.
  2. Select all of the category nodes, sorted alphabetically.
  3. 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">&#187; </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: , , , ,

0 Comments »

Filmstrip Border