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?
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.
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:
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/
