Electronic Collaboration Expertise since 1994

Initialize a Null NotesDocumentCollection

May 22 2010 01:31:18 PM
There are times when it is handy to start will a null collection in a NotesDocumentCollection., for example when you want to add documents to a collection programmatically based on something other than selected documents in a view.

Two good methods for doing this are:

Method 1:

Dim session As New NotesSession
Dim db As NotesDatabase
Dim collection As NotesDocumentCollection
               
Set db = session.CurrentDatabase
Set collection = db.GetProfileDocCollection("The name of a non-existent profile document")


Method 2:

Dim session As New NotesSession
Dim db As NotesDatabase
Dim collection As NotesDocumentCollection
Dim futureDate As New NotesDateTime("01/01/3001")
               
Set db = session.CurrentDatabase
Set collection = db..Search({@Nothing},futureDate, 0)

Method 1 is my usually preferred method if the database does not have a large number of profile documents.
Method 2 can be faster if there are a large number of profile documents (i.e. tens of thousands or more.)

Screen Resultions Higher than 1024x768 for Linux under Parallels 4

January 19 2010 01:26:01 PM
By default Parallels allocates a very small amount of video memory for Linux installs. Simply increase the amount of available video memory to allow for the largest desired resolution. With the Parallels Tools installed you will now be able to scale the window to resolutions higher than 1024x768.

Creating an RSS Feed from a Facebook Wall

October 24 2009 02:48:42 PM
I'm not the first one to run into this problem, but in searching around I couldn't actually find anyone who had actually bothered to solve the problem: how to get an RSS feed from a publicly accessible facebook wall. The facebook wall can either be a fan-page wall or a publicly accessible group's wall.

In my case I wanted to pull posts from various fan-page and group walls into a Drupal aggregator page. The following perl script does the job by scraping through the HTML for certain key id names and class names in facebook's mark-up and extracting the text and turning it into a valid RSS feed.

The script is called with the following syntax (assuming the file is named "facebookrss.pl"):

For pages with the standard URL syntax:

http://[yourserver]/[yourpath]/facebookrss.pl?url=/pages/pagename/[numericpageid]
&title=[your desired RSS feed title]&description=[your desired RSS feed description]

eg:
http://[yourserver]/[yourpath]/facebookrss.pl?url=/pages/Duct-Tape/91090174601&title=Duct%20Tape&description=For%20all%20actual%20repairs

For pages with "friendly" URL's:

http://[yourserver]/[yourpath]/facebookrss.pl?url=/[friendly name]&title=[your desired RSS feed title]&description=[your desired RSS feed description]

eg:
http://[yourserver]/[yourpath]/facebookrss.pl?url=/RedGreen&title=Red%20Green&description=If%20they%20don't%20find%20you%20handsome%20they%20better%20find%20you%20handy

For groups:

http://[yourserver]/[yourpath]/facebookrss.pl?url=/group.php?gid=[numericgroupid]&title=[your desired RSS feed title]&description=[your desired RSS feed description]

eg:
http://[yourserver]/[yourpath]/facebookrss.pl?url=/group.php?gid=2223250628&title=Duct%20Tape&description=For%20all%20actual%20repairs

Here's the code (be careful about extraneous line breaks caused by the browser - they should be fairly obvious):

#!/usr/bin/perl -w
use CGI qw(:standard);
use LWP::Simple qw(getstore);
use LWP::UserAgent;
use HTML::TokeParser::Simple;
use Time::Format qw(%time %strftime %manip);
use Date::Manip qw(ParseDate UnixDate DateCalc);
use HTML::Entities;
use URI::Escape;
use Encode;

$true = 1;
$false = !1;

#set user agent
$ua = LWP::UserAgent->new;
$ua->agent("Mozilla/2.02E (Win95; U)");

print header ('text/xml');
#print header ('text/plain'); #debug

$title = param("title");
$base = "http://www.facebook.com";
$suffixToken = "?";
$suffix = "v=wall";
$url = $base.param("url");
if ($url =~ /group\.php/) {
 $url = uri_unescape($url);
 $suffixToken = "&";
}
$url .= $suffixToken.$suffix;
$description = param("description");
$pubDate = 0;

#retrieve the page
$file = "";
$request = HTTP::Request->new(GET => $url);
$response = $ua->request($request);
#fix encoding if HTTP response header doesn't match (shouldn't matter with FB though)
if($response->is_success) {
 $content = $response->content;
 $encoding = "utf8"; # assume this is the default
 if($content =~ /encoding="([^"]+)"/) {
   $encoding = $1;
 }
 $file = $response->decoded_content((charset => $encoding));
}

#properly encode for RSS
$encTitle=HTML::Entities::encode($title);
$encUrl=HTML::Entities::encode($url);
$encDescription=HTML::Entities::encode($description);

# begin RSS output
print "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>
<rss version=\"2.0\">
<channel>
<title>$encTitle</title>
<link>$encUrl</link>
<description>$encDescription
</description>
";

# Parse HTML and pull entires
$p = HTML::TokeParser::Simple->new(\$file);

my $output = "";
my $count = 0;
%items = ();
$pubDate = "1/1/1970";
$realTimeText = "1/1/1970";

while (my $token = $p->get_token) {
 if (@$token[0] eq "S") {
   #when we hit div class used by FB for all wall posts it's time to get busy
   if (($token->as_is =~ /UIIntentionalStory/)) {
     $content = "";
     $firstMediaItem = $true;
     $realTimeFlag $false;
     $token = $p->get_token;
     while  ( not( $token->as_is =~ /id="commentable_item_|UIActionLinks/)  ) {
       #put a space in whenever we hit a closign div tag to avoid mashing text together
       if ($token->is_end_tag("div")) {
         $content .= " ";
       }
       #add a newline before "attachments"
       if ($token->is_start_tag("div") && ($token->as_is =~ /UIStoryAttachment/) && $firstMediaItem) {
         $firstMediaItem = $false;
         $content .= "<br />";
       }
       #add a newline before the content of the wall post
       if ($token->is_start_tag("span") && ($token->as_is =~ /UIStory_Message/)) { #
         $content .= "<br />";
       }
       #skip over tags we want to ignore and strip out useless attributes
       if ( not($token->is_end_tag("h3")) &&
            not($token->is_tag("span")) &&
            not($token->is_tag("div")) ) {
         $token->delete_attr("onclick");
         $token->delete_attr("class");
         $token->delete_attr("id");
         $token->delete_attr("data-ft");
         #if the URL in a link is a relative link to a photo, make it absolute
         if  ($token->is_start_tag("a")) {
           $href = $token->get_attr("href");
           if ($href) {
             $href =~ s/\/photo\.php/http:\/\/www.facebook.com\/photo\.php/;
             $token->set_attr("href",$href);
           }
         }
         #hacking a little spacing aroung the profile image
         #even though presentation *should* be completely separate from content
         if  ($token->is_start_tag("img")) {
           $href = $token->get_attr("src");
           if ($href =~ /http:\/\/profile/) {
             $token->set_attr("align","left");
             $token->set_attr("hspace","10");
             $token->set_attr("class","picture");
             $token->set_attr("style","margin-right: 10px !important;");
           }
         }
       #Skip over "read more" tokens otherwise keep the token as-is
       if ($token->as_is =~ /^read more$/i) {
         $content .= "";
       } else {
         $content .= $token->as_is;
       }
       } #end if ( not($token->is_end_tag("h3")) &&...
       $token $p->get_token;
     } #end  while  ( not( $token->as_is =~ /id="commentable_item_|UIActionLinks/)
     $token = $p->get_token;
     while ( not( $token->is_text )) {
       $token $p->get_token;
       #check for a real timestamp
       if ($token->is_tag("abbr") && ($token->as_is =~ /timestamp/i)) {
         $realTimeFlag = $true;
         $realTimeText = $token->get_attr("title");
       }
     }
     #if we have a real timestamp, use it, otherwise parse the time text
     if ($realTimeFlag) {
     $timeText = $realTimeText;
     } else {
       $timeText = $token->as_is;
         if ($timeText =~ /about/) {
           $timeText =~ s/\sat/,/;
         }
     }
     my $date = ParseDate($timeText);    
     my $epochDate UnixDate($date, "%s");
     # parsing the text sometimes means it will be a week off,
     # i.e. *next* Friday instead of *last* Friday
     # so if we get a future date, subtract a week
     if ($epochDate > time()) {
             $epochDate = $epochDate - 604800;
             $date = DateCalc($date, "-1 week");
     }
     my $epochPubDate = UnixDate($pubDate, "%s");
     # keep track of the newest post, this will be our lastBuildDate
     if ($epochDate > $epochPubDate) {$pubDate = $date};
     my $dateText = UnixDate($date, "%a, %e %b %Y %H:%M:%S %Z");
     my $link = $url;
     my $linkText = "View wall";
     my $title = "New wall post";
     if ($content =~ /(\/note\.php.+?)"/) {
       $link = "http://www.facebook.com".$1;
       $title "New note";
       $linkText = "View original note";
     }
     if ($content =~ /(\/album\.php.+?)"/) {
       $link = "http://www.facebook.com".$1;
       $title "New photos";
       $linkText = "View album";
     }
     
     $content .= "<small><br /><a href=\"$link\">$linkText</a><br /></small>";
     
     #use the epoch time the key for a hash of hashes that we can sort later
     $items{$epochDate}{Link} = HTML::Entities::encode($link);
     $items{$epochDate}{Text} = HTML::Entities::encode($title);
     $items{$epochDate}{Description} = "<![CDATA[".$content."]]>";
     $items{$epochDate}{Date} = $dateText ;
   } #end if (($token->as_is =~ /UIIntentionalStory/))
 } else {
   next;
 } #end if (@$token[0] eq "S")
}

# output the rest of the RSS
$pubDateText = UnixDate($pubDate, "%a, %e %b %Y %H:%M:%S %Z");
print "<lastBuildDate>$pubDateText</lastBuildDate>\n";

#sort the posts by last modified time using the epoch times
@itemKeys = reverse sort keys %items;

#write out well formed items, now in the right order
foreach $epoch (@itemKeys) {
 my $item = $items{$epoch};
 print "<item>\n";
 print "   <title>";
 print $items{$epoch}{Text};
 print "</title>\n";
 print "   <description>";
 print $items{$epoch}{Description};
 print "</description>\n";
 print "   <pubDate>";
 print $items{$epoch}{Date};
 print "</pubDate>\n";
 print "   <link>";
 print $items{$epoch}{Link};
 print "</link>\n";
 print "</item>\n";
}

# End of the RSS feed
print "
</channel>
</rss>
";

exit (0);

Does Your E-Mail Signature Smell Like Spam?

September 23 2009 02:12:00 PM
Sometime around five or so years ago, it became du rigueur to have e-mail signatures in a format not unlike this:
Image:Does Your E-Mail Signature Smell Like Spam?

Joe Salesman

Marketing Associate


joe@companyxyz.ca

Ph.  +1 (604) 555-1234
Fax +1 (604) 555-4321
http://www.companyxyz.ca

Notice Regarding Confidentiality of Transmission: This message is intended only for the person to which it is addressed and contains information that is privileged and confidential. If you are not the intended recipient, you are hereby notified that it is actually our mistake and you can do whatever you want since disclaimers like this don't constitute any kind of enforceable contract
Sadly the marketing types that continue to insist on this type of HTML-based signature in the name of "corporate branding"  and legal departments that insist on the unenforceable disclaimers are, in fact, causing untold amounts of valid e-mails to be intercepted by anti-spam filters.

In more recent years, as spam-filtering has become standard, spammers adapted to the text-based spam-filtering software by instead embedding a single image bearing the text of their message plus some innocuous random prose. Forces on the anti-spam side of things retaliated by
  1. looking for messages with a single in-line image with a small amount of text,
  2. checking if the in-line image is a GIF,
  3. checking for a block of text that is in a fainter or smaller font after the in-line image, and
  4. checking if the in-line image is predominantly text or text-like shapes (since the spammers frequently obfuscate the text to avoid even higher scores from OCR aware spam-scanners.)

The circa-2004 signatures hit all of these criteria, unnecessarily raising the aggregate spam score for the message as a whole. Toss something like this on the end of a valid e-mail to a client that bears something like a price list with a dollar-signs and maybe some three or four letter acronyms that are going to trip even more spam rules designed around thwarting pill-pushers and bogus stock promotions and the chances of the message not getting though get even higher.

And then there is the problem if it does get through and a conversation ensues with each participant's mail client quoting the preceding signatures and dutifully tagging on yet another such that the few hundred bytes of actual content are unreadably interspersed among kilobytes of corporate logos and verbose disclaimers. It may be trivial in the context of a single message, but when every single message is orders of magnitude larger than it needs to be to carry its actual content, it compounds into storage problems and compliance problems.  

I say it's time to go back to the future. Once upon a time, back in the days of Usernet and text-only e-mails netiquette demanded that signatures be no longer than four 72-character lines of text and should begin with two dashes and a space (i.e. "-- ".)

The short signatures were to preserve the very much more limited bandwidth and storage, which remains a good thing, but they also have the added advantage now of being more spam-scanner-friendly and readable in a long thread. Readability would be further enhanced with the return of the dash-dash-space convention, since this would allow redundant signatures to easily be stripped off programmatically.

In short, this is what a good signature should look like:

--
Joe Salesman, Marketing Associate
CompanyXYZ - http://www.companyxyz.com
joe@companyxyz.com
Phone: +1 (604) 555-1234 / Fax: +1 (604) 555-4321

Or even better:

--
Michael R. Barrick
mbarrick@mbarrick.com
http://www.mbarrick.com

Certification and Quantifying Relevant Skills

September 3 2009 10:00:03 AM
There is a lot of emphasis put on certifications in the hiring process. To a certain extent I can understand this, after all, Human Resources professionals are not Information Technology professionals and certification provides the only reliable interface they have for a quantifying whether an individual really knows something about the technology they claim to understand.

In my experience, though, I've run into far to many "certified" IT professionals that work wholly on rote procedure and couldn't think their way out of an Aristotelian syllogism, let alone solve an "out of the box" problem. Like so many students do with testing throughout their academic careers, certification tests are studied for, taken, and the content the test is intended to verify is promptly forgotten.

Technology changes rapidly, and far more important than the ability to memorize a procedure is the ability to think and creatively solve problems. Problems are a reality of IT, whether one is an administrator or a programmer. Time is most profitably spent in preventing problems which, while a largely thankless task, requires skills that certification can't test for - the ability to grasp correlations and patterns and anticipate outcomes.

There is way to test for this, though. Most IQ tests include components that test specifically for these skills. I recently joined the International High IQ Society and below are my scores based on the TA³ "Test for Above Average Abilities" and the attendant write-up for the relevant sections:
Verbal Analogies  154
     
The verbal analogies subtest assesses verbal comprehension, language development, learning ability, long-term memory, richness of ideas, and concept formation. Despite the fact that this subtest only measures crystallized intelligence, is culturally dependent, and doesn't involve logical, mathematical, spatial or any other specialized aptitude, it has a higher correlation to overall IQ than any of our subtests.

Spatial Visualization 136        


Spatial visualization involves isolating and identifying objects hidden in visual images and mentally rotating these objects to determine if they are identical in form (understanding the relationship of objects in an embedded figure...

...Your spatial visualization score is indicative of someone with an extraordinary ability to use inductive reasoning to extract patterns in life, perceive relationships, and draw conclusions.

Visual Sequencing 142        


This subtest is comprised of six sequential matrices questions which assess nonverbal reasoning. Nonverbal reasoning is the ability to perceive relationships, to draw conclusions (inductive reasoning), to formulate and test hypotheses, to use analytic reasoning, and your long term information retrieval...

...Your visual sequencing score is indicative of someone with a profound ability to visualize objects and situations in your mind and to manipulate those images, which is a cognitive skill vital in many career fields.
These scores are all beyond the second standard deviation, higher than over 99.5% of the population.  And, yes, this is blatant self-promotion.