ColdFusion hCard Microformats Custom Tag

I a huge fan of of microformats (or µF), if you're not familiar with them take a look at www.microformats.org and the entry at Wikipedia.

Basically it's a way of marking up HTML to give it more more meaning, think of it as the semantic web (with a small s) that is available now.

hCard is a HTML representation of vCard and while no browers currently support it natively, the next versions of Firefox and IE will support it. There are also plugins currently available for Firefox (Operator and Tails ) and Safari (various bookmarklets) and IE (various favlets).

So what does a hCard look like?

view plain print about
1<div class="vcard">
2    <div class="fn">Justin Mclean</div>
3    <div class="adr"><span class="street-address">170 Campbell St</span>, <span class="locality">Sydney</span> <span class="region">NSW</span> <span class="postal-code">2010</span></div>
4    <div class="tel"><span class="type"><abbr title="work">Phone:</abbr></span> <span class="value">(02) 9368 1014</span></div>
5    <div class="tel"><span class="type"><abbr title="cell">Mobile:</abbr></span> <span class="value">0416119342</span></div>
6    <div>Email: <a class="email" href="mailto:justin@classsoftware.com">justin@classsoftware.com</a></div>    
7</div>

As you can see you can easily mark up an address as a microformat and then style it with CSS.

If you have several 100 addresses that you needed to convert it would get a bit tedious doing it all by hand. That's why I've created a ColdFusion custom tag to do the work for you.

view plain print about
1<!---
2    Name : cfhcard
3    Author : Justin Mclean
4    Copyright : Class Software 2007 (http://www.classsoftware.com)
5    License : Licensed under Creative Commons attribution license
6--->

7<cfif thisTag.executionMode is "end">
8    <cfscript>
9    // function to take an address string and break it up into it's elements
10
    function parseAddress(addressstring)
11    {
12        var    nolines = 0;
13        var    lastline = '';
14        var    line = '';
15        var address = structnew();
16        
17        // split address on commas and new lines
18
        newline = chr(13) & chr(10);
19        newlinecomma = '#newline#,';
20        
21        if (find(',', addressstring) or find(newline,addressstring)) {
22            nolines = listlen(addressstring, newlinecomma);
23            
24            address.address = listfirst(addressstring, newlinecomma);
25            lastaddress = '';
26            
27            // try and find state postcode line
28
            for (i = 2; i lte nolines; i = i + 1) {
29                lastline = listgetat(addressstring, i-1, newlinecomma);
30                line = trim(listgetat(addressstring, i, newlinecomma));
31                
32                // look for line matching city state and postcode            
33
                if (refind('[A-Z|a-z]+ [A-Z|a-z]+ [0-9]+$',line)) {
34                    address.city = trim(listfirst(line, ' '));
35                    address.state = trim(listgetat(line, 2, ' '));
36                    address.postcode = trim(listlast(line, ' '));
37                    break;
38                }
39                // look for line matching state and postcode    
40
                else if (refind('[A-Z|a-z]+ [0-9]+$',line)) {
41                    address.address = lastaddress;
42                    address.city = trim(lastline);
43                    address.state = trim(listfirst(line, ' '));
44                    address.postcode = trim(listlast(line, ' '));
45                    break;
46                }
47                else {
48                    lastaddress = address.address;
49                    address.address = '#address.address##line##newline#';
50                }
51            }
52            
53            // check for country if we still have lines to go
54
            if (i lt nolines) {
55                line = trim(listgetat(addressstring, i+1, newlinecomma));
56                if (refind('[A-Z|a-z]+ [0-9]+$',line)) {
57                    address.country = line;
58                }            
59            }
60        }
61            
62        return address;
63    }
64    
</cfscript>
65    
66    <cfset content = thisTag.GeneratedContent>
67
68    <cfscript>
69        // find address lines assume they comes first
70
        newline = chr(13) & chr(10);
71        content = replace(content, ',', newline, 'ALL');
72        endaddress = 0;
73        address = structnew();
74        addressstr = '';
75
76        nolines = listlen(content, newline);
77        for (i=2; i lte nolines; i = i + 1) {
78            line = trim(listgetat(content, i, newline));
79            addressstr = '#addressstr##line##newline#';
80                
81            // look for (city) state postcode line
82
            if (refind('[A-Z|a-z]+ [0-9]+$', line) or refind('[A-Z|a-z]+ [A-Z|a-z]+ [0-9]+$', line)) {
83                endaddress = i;
84                break;
85            }
86        }
87    
88        // check if next line could be country
89
        if (endaddress gt 0) {
90            line = listgetat(content, endaddress + 1, newline);
91            if (refind('[A-Z][a-z]+$', line)) {
92                addressstr = '#addressstr##line##newline#';
93                endaddress = endaddress + 1;
94            }
95        }
96        // parse address
97
        address = parseAddress(addressstr);
98        
99        // set other address fields
100
        address.name = listfirst(content, newline);
101        
102        // check other lines for other known values
103
        for (i = endaddress + 1; i lte nolines; i = i + 1) {
104            line = trim(listgetat(content, i, newline));
105
106            if (refind('[A-Z|a-z]+[\:|\-| ]+[0-9|\-|\+|\(|\)| ]+$',line)) {
107                label = trim(listfirst(line, ':-'));
108                if (lcase(label) is 'mobile' or lcase(label) is 'm') {
109                    address.mobile = trim(listlast(line, ':-'));
110                }
111                if (lcase(label) is 'phone' or lcase(label) is 'telephone' or lcase(label) is 'work' or lcase(label) is 'p' or lcase(label) is 't' or lcase(label) is 'w') {
112                    address.phone = trim(listlast(line, ':-'));
113                }                
114            }
115            else if (refind('[0-9|\-|\+\(|\)| ]+$',line)) {
116                address.phone = line;
117            }
118            else if (refind('[A-Z|a-z]+[\:|\-| ]+[A-Z|a-z|0-9]+\@[A-Z|a-z|0-9|\.]+$', line)) {
119                label = listfirst(line, ':-');
120                if (lcase(label) is 'email') {
121                    address.email = trim(listlast(line, ':-'));
122                }
123            }
124            else if (refind('[A-Z|a-z|0-9]+\@[A-Z|a-z|0-9|\.]+$', line)) {
125                address.email = line;
126            }            
127        }
128    
</cfscript>
129    
130    <cfsavecontent variable="hcard">
131    <cfoutput>
132<div class="vcard">
133    <div class="fn">#address.name#</div>
134    <div class="adr"><span class="street-address">#address.address#</span>, <span class="locality">#address.city#</span> <span class="region">#address.state#</span> <span class="postal-code">#address.postcode#</span><cfif isdefined("address.country")> <span class="country-name">#address.country#</span></cfif></div>
135    <cfif isdefined("address.phone")><div class="tel"><span class="type"><abbr title="work">Phone:</abbr></span> <span class="value">#address.phone#</span></div>
136    </cfif>
137    <cfif isdefined("address.mobile")><div class="tel"><span class="type"><abbr title="cell">Mobile:</abbr></span> <span class="value">#address.mobile#</span></div>
138    </cfif>    
139    <cfif isdefined("address.email")><div>Email: <a class="email" href="mailto:#address.email#">#address.email#</a></div>
140    </cfif>
141</div>
142    </cfoutput>
143    </cfsavecontent>
144        
145    <cfset thisTag.GeneratedContent = hcard>
146</cfif>

The above custom tag will take any plain text address and convert it to a hCard microformat, and while not perfect it understand a wide range of address formats and telephone and email addresses.

Currently the tag assumes Australian style addresses but it wouldn't be too hard to extend to other address formats/styles.

Here is a example of it's use:

view plain print about
1<cf_hcard>
2Justin Mclean
3170 Campbell St, Sydney, NSW 2010, Australia
4email: justin@classsoftware.com
5mobile: 0416119342
6phone: (02) 9368 1014
7</cf_hcard>
8
9<cf_hcard>
10Justin Mclean
11170 Campbell St
12Sydney
13NSW 2010
14Email: justin@classsoftware.com
15Mobile: 0416119342
16</cf_hcard>
17
18<cf_hcard>
19Justin Mclean
20170 Campbell St
21Sydney NSW 2010
22M: 0416119342
23W: +61 2 9368 1014
24</cf_hcard>
25
26<cf_hcard>
27Justin Mclean
28170 Campbell St
29Sydney
30NSW 2010
31justin@classsoftware.com
32041 6119 342
33</cf_hcard>

Update - This project can now be found at:
http://cfhcard.riaforge.org/

TweetBacks
Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Thanks for this post. I've been using Microformats and wanting to do more, but had not taken the time to automate the markup process. This gives me some great ideas.
# Posted By Kevin Parker | 10/15/09 2:05 PM