Login | Register   
LinkedIn
Google+
Twitter
RSS Feed
Download our iPhone app
TODAY'S HEADLINES  |   ARTICLE ARCHIVE  |   FORUMS  |   TIP BANK
Browse DevX
Sign up for e-mail newsletters from DevX


advertisement
 

Creating Simple Firefox "Add-ons" with Ubiquity : Page 2

Developing add-ons for Firefox can be tedious, but it's not hard.


advertisement
Building an Ubiquity "Macro" Filter
A useful tool is a regular expression filter that analyzes a web page for specific pattern matches and replaces them with alternative content. For example, if you follow the U.S. presidential election very closely, you might find it useful to highlight the name of the presidential candidates when they appear on a web page, along with a title or link to an external site.

To accomplish this, you can create a Ubiquity macro command. This command takes one argument, the URL of an XML-based dictionary file, downloads that file, then parses it out to create a set of regular expressions (regexes) and their corresponding replacement text. In turn, each filter applies to either a selection of text or the entire page, replacing the regexes with their corresponding replacements.

First, you need to obtain the Ubiquity add-on prototype from Mozilla Labs. Keep in mind that this is a working prototype—there's still a significant amount of work to be done before it’s ready for prime time production, so don’t plan on incorporating ubiquity components in your applications just yet.



After you install the add-on and reboot Firefox, you can make use of the incorporated editor at chrome://ubiquity/content/editor.html. the chrome:// protocol indicates that it is incorporated into the browser itself rather than being downloaded from an external site, making it possible to develop (most) applications even when you're not online. To add new scripts, you simply type them into the editor, which automatically saves content.

Author's Note to Mozilla: While these features are cool and AJAX-y, and provide the option to disable this and set up a Save button, it's just too easy to inadvertently wipe out your work or save something to an incomplete state that could be potentially destructive with the implicit autosave.





Before getting into the Ubiquity code, it's worth looking at the "dictionary" XML file (see Listing 1). The dictionary is divided into multiple entries, each of which has an associated term (the regular expression pattern to be matched) and a corresponding expression (<expr>) that contains the replacement text. In most cases, the text is supported by CDATA blocks holding HTML replacement content. If the term pattern you were attempting to match contains HTML or XML content, then you can also use the <![CDATA[ ]]= constructs in the <term> element itself. The delimiters do not, of course, appear when the text of a <term> or <expr> node is resolved.

Additionally, each entry has two attributes:

  • global: This attribute is used to determine whether to replace the term pattern throughout the selection (which is usually the case) or whether the term should be replaced only upon its first appearance.
  • casesensitive: This attribute is used to ensure that a term pattern matches--regardless of whether that term's case is the same as the matched item. These alternate between yes and no values.

Save this file onto the web (you can save it locally as well) and record the URL (in this case as http://localhost:8080/exist/rest//db/sandbox/dictionary.xml). If you're distributing this with a specific directory, there is a defaultURL property that you can set in order to perform the default macro operations.

A command in Ubiquity is called a verb, and you can create it within the Ubiquity chrome editor using the CreateCommand() method on the CmdUtils object. CmdUtils is a generalized library of routines that simplify the development of Ubiquity verbs and nouns (think of nouns as data types). In particular, the CreateCommand() method is the most powerful method, as it allows you to build the full infrastructure for a given verb.

To create a command, you pass an object structure to the CreateCommand() method. The first part of this structure provides hooks used for documentation, as is illustrated by the fragment shown here.

CmdUtils.CreateCommand({ name:"macro", author: { name: "Kurt Cagle", email: "kurt@oreilly.com"}, contributors: ["Kurt Cagle"], license: "Apache License, v.2", homepage: "http://www.xforms.org/xrx/?q=ubiquity", description: "Performs regular expression replacements of web page content.", help: "The macro verb takes the URL of a dictionary file (which can be set up as a default using the
defaultURL property) and uses it to replace each regex term with its corresponding replacement value in either
a web page (if no content is selected) or within a given selection (if one has been).", ...

Of these, only the name is truly necessary, as this is the verb name itself used from the Ubiquity command line. The rest is used by user interfaces, such as that displayed by typing command-list into the Ubiquity command line.

Beyond these, the three next most important tags are the takes:, preview: and execute: tags. The takes: entry provides the name of the parameter(s) that both preview and execute will use that are passed in the Ubiquity command line:

takes:{"sourceURL":noun_arb_text},

where noun_arb_text indicates that the parameter information is contained in an object that exposes three values (text:, html:, data:), which are filled differently depending where the parametric information comes from.

The preview: tag is a function definition used to show a preview of the command within the Ubiquity window. This takes both the parameters passed in takes: along with a preview block ( pblock) that is a div within the Ubiquity window that you can shape to handle specific output. In this particular case, the preview function loads in the dictionary and displays each term and the corresponding replacement text:

preview:function(pblock,sourceURL){ this._src = CmdUtils.getHtmlSelection(); var table=<table/>; try{ this._parseEntries(sourceURL,this._src,function(entries,str){ for each (var entry in entries){ table.tr += <tr>td>{entry.term}</td><td>{entry.expr}</td></tr>; } pblock.innerHTML = table.toXMLString(); }); } catch(e){} },

In the above code, the CmdUtils.getHtmlSelection retrieves the specific selection's HTML content and passes it to the locally defined _parseEntries method, though in this particular case the information from the selection isn't used. However, Ubiquity creates a callback function, that is invoked after the entries are loaded. This callback then passes two things: the object descriptions of each entry in the dictionary and the string produced by parsing the selection contents against all of the entries. The routine then iterates over the entries and creates an HTML table, which it then turns into the inner content of the preview block.

The preview is displayed while people enter the command (which it means it's updated after every keystroke). Because it is possible that what's being typed will likely produce erroneous results, the preview should use a try/catch block to insure that only "safe" content creates a preview.

Pressing the return key invokes the execute command, with it's typical request either to alter the content of the browser window or to send a message to the pop-up message stack used by Ubiquity. Since it doesn't update the preview block in any way, this isn't passed into the execute function when it is invoked as a callback.

The execute command for macro similarly calls the local _parseEntries() method in order to replace the old content in the page with the replaced content:

execute:function(sourceURL){ this._src = CmdUtils.getHtmlSelection(); if(this._src==null){ var doc = CmdUtils.getDocumentInsecure(); this._src=doc.body.innerHTML; this._parseEntries(sourceURL,this._src,function(entries,str){doc.body.innerHTML=str}); } else { this._parseEntries(sourceURL,this._src,function(entries,str){CmdUtils.setSelection(str,{});}); } },

Again, notice the use of the anonymous callback. As both the preview and the execute function make use of common code, the only difference is how they utilize the results of the code.

Neither the execute: nor the preview: function are specifically necessary—an Ubiquity verb may be completely informational (counting the number of words in a selection, for instance) and as such an execute isn't needed. Similarly, a verb might not have any specific preview but which still performs an execute statement—such as a goto verb that allows users to bring up a given URL in a new tab.

Beyond these, it is also possible to place helper functions and properties within the object as well, making these available via the keyword. For instance, the _parseEntries() function combines the steps of retrieving the dictionary from the sourceURL, passing in the text from whatever source to be parsed, and to invoke a callback function that contains both the dictionary entries and the processed results string (see Listing 2).

This makes use of the jQuery() function (jQuery is available as part of the internal library) to perform an HTTP get() to retrieve the content from the URL, then creates the corresponding entries. Note that the $() method is simply a shorthand function for jQuery(), and is used for legibility purposes. This repeats over each entry, with the string buffer containing the original HTML to be replaced updated with the new content.

This allows you to process multiple regular expressions on the same block of text in the order that they're listed in the dictionary. Given that the replacement expressions can include parenthetical placeholders (denoted by $1, $2, ... $9) this makes it possible to do some very sophisticated recursive parsing upon the content block itself.

Note that two other local properties are defined on the verb: _src: contains the initial and transformed content, while defaultURL: holds the URL for the default dictionary you want to use whenever no URL is given.

The full verb definition is given in Listing 3, and can be downloaded from http://www.xforms.org/xrx/?q=ubiquity/macro.

 
Figure 1. Before and After: CNN political pages before and after the macro is applied.
The fruits of this labor appear in Figure 1, which shows side-by-side shots of the CNN’s Political page just prior to the 2008 presidential election. The first shows the page before the macro is applied and the second shows the results.

In this particular case, the last name of each candidate has changed color, and around their full names you can see a dotted line – rolling over those bounded boxes displays "Candidate for President." The potential for this capability is huge: you can receive content from external sources and rearrange (sometimes dramatically) content within a given web page. Additionally, there are mechanisms coming online that enables the use of such macros upon either browser startup or page reloading – these are still in the alpha stage for development, so it is not covered here. Similarly, there are a number of mechanisms within Ubiquity that allow for modification of the browser itself, meaning that Ubiquity scripts may ultimately replace the cumbersome and awkward XPIs currently used for building Mozilla plugins.

Ubiquity is still very much in early stages, but its long term implications as the foundation of a new type of web operating system make it a technology to watch very carefully. For more information, check out the Ubiquity Wiki.



Kurt Cagle is the managing editor for XMLToday.org and a contributing editor for O'Reilly Media. He is currently working on a book about XBRL. Follow him on Twitter at twitter.com/kurt_cagle.
Comment and Contribute

 

 

 

 

 


(Maximum characters: 1200). You have 1200 characters left.

 

 

Sitemap