<?xml version="1.0" encoding="ISO-8859-1"?><!-- OPML generated by Radio UserLand v8.0.8 on Mon, 01 Mar 2004 22:46:26 GMT -->
<opml version="1.1">	<head>		<title>eaTour.opml</title>		<dateCreated>Sat, 28 Feb 2004 23:14:00 GMT</dateCreated>		<dateModified>Mon, 01 Mar 2004 22:46:25 GMT</dateModified>		<ownerName>Marc Barrot</ownerName>		<ownerEmail>info@prec-it.com</ownerEmail>		<expansionState></expansionState>		<vertScrollState>1</vertScrollState>		<windowTop>44</windowTop>		<windowLeft>1</windowLeft>		<windowBottom>765</windowBottom>		<windowRight>640</windowRight>		</head>	<body>		<outline text="#title &quot;Journey Inside a Tool&quot;"/>		<outline text="Welcome to the enhancedAggregator tool code tour. The visit will take you to all the components of a typical Usertalk tool for &lt;a href=&quot;http://radio.userland.com/&quot; target=&quot;_blank&quot;&gt;Radio Userland&lt;/a&gt;, and will provide you with scenic views of Radio's beautiful news aggregator and its unique driver architecture.">			<outline text="&lt;a href=&quot;http://updates.prec-it.com/download/enhancedAggregator.root&quot; target=&quot;_blank&quot;&gt;Download&lt;/a&gt; enhancedAggregator.root to your Tools folder if you haven't already done so."/>			<outline text="Read the general &lt;a href=&quot;http://radio.weblogs.com/0104487/outlines/radio/enhancedAggregator.html&quot; target=&quot;_blank&quot;&gt;guidelines&lt;/a&gt; on how to add new format and module drivers to the tool."/>			<outline text="Userland provides general information on tools creation &lt;a href=&quot;http://frontier.userland.com/tools&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;."/>			<outline text="Roger Cadenhead's &lt;a href=&quot;http://www.cadenhead.org/workbench/kickstart/&quot; target=&quot;_blank&quot;&gt;Radio Userland Kickstart&lt;/a&gt; has a whole chapter devoted to tools writing."/>			</outline>		<outline text="The tour starts in &lt;code&gt;enhancedAggregatorThread.script&lt;/code&gt;.">			<outline text="This script is launched by Radio as soon as the tool is installed, and executed in the background as a separate &lt;a href=&quot;http://pages.sbcglobal.net/mattneub/frontierDef/ch21.html#14577&quot; target=&quot;_blank&quot;&gt;thread&lt;/a&gt; dedicated to the enhancedAggregator tool.">				<outline text="For this to happen, the &lt;code&gt;enhancedAggregatorThread.enabled&lt;/code&gt; boolean must be set to &lt;i&gt;true&lt;/i&gt;."/>				</outline>			<outline text="&lt;code&gt;bundle #1&lt;/code&gt; registers the thread in Radio's temp table, I placed it there for debugging purposes, but it has no active role."/>			<outline text="&lt;code&gt;bundle #2&lt;/code&gt; launches the tool's &lt;code&gt;instal&lt;/code&gt; script (colonials please note the proper spelling :-) in a dedicated thread. &lt;code&gt;instal&lt;/code&gt; is responsible for hooking all enhancedAggregator's features to their proper locations in Radio's data structure.">				<outline text="The script uses the enhancedAggregator table in Radio's temp table as a semaphore: since Radio empties the temp table at startup, &lt;code&gt;instal&lt;/code&gt; is launched only if the enhancedAggregator table does not already exist. This ensures that &lt;code&gt;instal&lt;/code&gt; is launched only once at startup, no matter what."/>				<outline text="The &lt;code&gt;instal&lt;/code&gt; script is run in a separate thread so that it may lead an independant life, even updating itself if autoUpdate is enabled, without any backward effect on the main enhancedAggregator background thread."/>				</outline>			<outline text="The main background thread is supposed to live as long as the tool is active, therefore the script's &lt;code&gt;bundle #3&lt;/code&gt; is an endless loop.">				<outline text="Ending the script while the tool is active would cause Radio's &lt;a href=&quot;http://frontier.userland.com/stories/storyReader$254&quot; target=&quot;_blank&quot;&gt;scheduler&lt;/a&gt; to restart it within 1 minute, which takes longer than dozing."/>				</outline>			</outline>		<outline text="Now proceed to the &lt;code&gt;enhancedAggregatorsuite.instal&lt;/code&gt; script.">			<outline text="Leaving &lt;code&gt;bundle #1&lt;/code&gt; on your left, first check out &lt;code&gt;bundle #2&lt;/code&gt;, which instals the tool's user preferences by calling the &lt;code&gt;buildPrefs&lt;/code&gt; local function.">				<outline text="The tools &lt;i&gt;default&lt;/i&gt; preferences are located in &lt;code&gt;enhancedAggregatorData.prefs&lt;/code&gt;. They are booleans that govern the enabling or disabling of all the tool's features."/>				<outline text="Since the preferences are user &lt;i&gt;modifiable&lt;/I&gt;, we want to store their values in a distinct location, and make sure that we do not &lt;i&gt;overwrite&lt;/i&gt; them with their default values if they already exist."/>				<outline text="I chose to store the user prefs in an enhancedAggregator subtable of aggregatorData.root, the main database for Radio's aggregator.">					<outline text="Since we are modifying a table outside the scope of our own tool database, we will have to make sure we clean up after ourself when the tool is uninstalled."/>					<outline text="I could have chosen to store user prefs in a dedicated database file, or in a user subtable of enhancedAggregatorData instead."/>					</outline>				</outline>			<outline text="&lt;code&gt;bundle #3&lt;/code&gt; instals our first RSS 2.0 &lt;a href=&quot;http://frontier.userland.com/rss2ModuleSupportForXmlAggregator&quot; target=&quot;_blank&quot;&gt;module driver&lt;/a&gt;: support for &lt;a href=&quot;http://matt.blogs.it/specs/ENT/1.0/&quot; target=&quot;_blank&quot;&gt;ENT 1.0&lt;/a&gt; topics.">				<outline text="Since ENT 1.0 support can be turned on or off in the tool's preferences desktop website page, the actual job is turned over to a dedicated script: &lt;code&gt;enhancedAggregatorSuite.ent.instal&lt;/code&gt;."/>				</outline>			<outline text="&lt;code&gt;bundle #4&lt;/code&gt; instals the callback script necessary to include topics in Radio's desktop News page.">				<outline text="The way Radio's aggregator is designed, the module driver installed in bundle #3 retrieves the namespace specific tags' content and stores it into a &lt;i&gt;compilation&lt;/i&gt; table for later consumption by other programs, while the &lt;code&gt;storyArrived&lt;/code&gt; &lt;a href=&quot;http://frontier.userland.com/xmlAggregator&quot; target=&quot;_blank&quot;&gt;callback script&lt;/a&gt; retrieves the compiled information and updates the aggregator's internal data structure."/>				<outline text="We are adding the address of our script, &lt;code&gt;enhancedAggregatorSuite.ent.callbacks.aggregator.storyArrived&lt;/code&gt;, to the stack of storyArrived callbacks set up by Radio in aggregatorData.root.">					<outline text="Since I've selfishly and unwittingly used &lt;i&gt;enhancedAggregator&lt;/i&gt; to label the entry for ENT topics support, you would have to use another string, possibly the module's name or URI, to insert a callback to support any additional RSS 2.0 module."/>					</outline>				</outline>			<outline text="&lt;code&gt;bundle #5&lt;/code&gt; instals a &lt;a href=&quot;http://radio.userland.com/discuss/msgReader$11975&quot; target=&quot;_blank&quot;&gt;format driver&lt;/a&gt; for &lt;a href=&quot;http://www.atomenabled.org/developers/syndication/atom-format-spec.php&quot; target=&quot;_blank&quot;&gt;Atom&lt;/a&gt; feeds.">				<outline text="The actual job is handled by &lt;code&gt;enhancedAggregatorSuite.atom.instal&lt;/code&gt;."/>				</outline>			<outline text="&lt;code&gt;bundle #6&lt;/code&gt; instals another module driver for &lt;a href=&quot;http://www.esfstandard.org/&quot; target=&quot;_blank&quot;&gt;ESF&lt;/a&gt; events.">				<outline text="The actual job is handled by &lt;code&gt;enhancedAggregatorSuite.esf.instal&lt;/code&gt;, similar to  ent and atom installations."/>				</outline>			<outline text="&lt;code&gt;bundle #7&lt;/code&gt; instals the tool's un-installation code.">				<outline text="The uninstallation code performs the reverse of the &lt;code&gt;instal&lt;/code&gt; script."/>				<outline text="The trick is it must execute even if the tool's database itself, enhancedAggregator.root, is no longer in the Tools folder."/>				<outline text="So our first step is to copy the &lt;code&gt;uninstal&lt;/code&gt; script to the scratchpad table in Radio.root: it shall remain behind until needed, whatever happens."/>				<outline text="Our second step is to instal the address of our newly created scratchpad uninstal script in the afterUninstall callbacks stack in user.tools.callbacks.">					<outline text="This way, the tool's uninstal script is executed every time Radio uninstalls a tool, something that happens under several circumstances, more about this later."/>					</outline>				</outline>			<outline text="&lt;code&gt;bundle #8&lt;/code&gt; instals the tool's update table in its proper location in user.rootUpdates.servers.">				<outline text="We only insert it in if it's not there already."/>				<outline text="The update table is part of the tool framework's standard update mechanism.">					<outline text="It defines which server to retrieve updated parts from, and which method to invoke on that server via xml-rpc, through which port."/>					<outline text="I've defined all the proper values in enhancedAggregatorData.rootUpdateTable. The update server is a Frontier system operated from Downtown Manhattan by my company, Precision IT Management, Inc."/>					</outline>				</outline>			<outline text="Finally, our last section, &lt;code&gt;bundle #9&lt;/code&gt; updates the tool if the autoUpdate user preference is set to true.">				<outline text="The &lt;code&gt;update&lt;/code&gt; script is copied to the temp table and the copy is launched in a separate thread, since the update mechanism is liable to download an updated version of the &lt;code&gt;update&lt;/code&gt; script, which would then need to replace the original, without interrupting the current thread."/>				</outline>			</outline>		<outline text="The tool is now properly installed, our next station is the &lt;code&gt;enhancedAggregatorSuite.ui.prefs.edit&lt;/code&gt; script.">			<outline text="This script is responsible for modifying all the tool's user preferences through a desktop website page."/>			<outline text="When a user directs his/her web browser to &lt;i&gt;http://127.0.0.1:5335/enhancedAggregator/&lt;/i&gt;, Radio returns a web page created by merging  &lt;code&gt;enhancedAggregatorWebsite.index&lt;/code&gt; into &lt;code&gt;enhancedAggregatorWebsite.#template&lt;/code&gt;.">				<outline text="The &lt;code&gt;#template&lt;/code&gt; outline defines the general structure of the HTML page, with some specific CSS rule definitions in the header section, the &lt;code&gt;{bodytext}&lt;/code&gt; macro being replaced by the content of &lt;code&gt;index&lt;/code&gt;."/>				<outline text="The &lt;code&gt;index&lt;/code&gt; text object contains whatever is returned by the &lt;code&gt;enhancedAggregatorSuite.ui.prefs.edit&lt;/code&gt; script."/>				</outline>			<outline text="The &lt;code&gt;edit&lt;/code&gt; script takes a single argument: the address of the &lt;i&gt;page table&lt;/i&gt;, an invaluable data structure holding all the elements of the HTTP request received by Radio's desktop web server.">				<outline text="To get a better look at the page table, uncomment debug bundle #1."/>				</outline>			<outline text="The main job of &lt;code&gt;edit&lt;/code&gt; is to return some HTML content to fit into the preferences page."/>			<outline text="This content is defined in the &lt;code&gt;enhancedAggregatorData.ui.prefs.template&lt;/code&gt; outline, and assigned to the &lt;code&gt;s&lt;/code&gt; local variable, which will eventually be returned by the script.">				<outline text="The preferences page is mainly an HTML form made of &lt;code&gt;INPUT type=&quot;checkbox&quot;&lt;/code&gt; fields."/>				</outline>			<outline text="It is a template itself: variables defined between ## tags will be replaced on the fly by the &lt;code&gt;edit&lt;/code&gt; script, according to the current values in the tool's user preferences table: this is the part of the script that executes &lt;i&gt;after&lt;/i&gt; the first &lt;code&gt;if&lt;/code&gt; statement."/>			<outline text="Here is the interesting part: the page's form is &lt;i&gt;submitted&lt;/i&gt; to the same &lt;i&gt;http://127.0.0.1:5335/enhancedAggregator/&lt;/i&gt; URL, generating an HTTP POST request instead of the HTTP GET request normally issued when the user first entered the URL in the browser or linked from another HTML page.">				<outline text="For more information on HTTP protocol requests, check out O'Reilly's &lt;a href=&quot;http://safari.oreilly.com/?x=1&amp;mode=section&amp;sortKey=title&amp;sortOrder=asc&amp;view=&amp;xmlid=1-56592-509-2&amp;open=true&amp;g=&amp;catid=itbooks.inet.http&amp;s=1&amp;b=1&amp;f=1&amp;t=1&amp;c=1&amp;u=1&amp;r=&amp;o=1&amp;page=0&quot; target=&quot;_blank&quot;&gt;HTTP: The Definitive Guide&lt;/a&gt; by David Gourley and Brian Totty."/>				</outline>			<outline text="Hence the importance of the first &lt;code&gt;if&lt;/code&gt; statement: if we are answering a POST request, then the page table's &lt;code&gt;requestBody&lt;/code&gt; string is not empty, and we can proceed to retrieving the form's content before returning anything.">				<outline text="&lt;code&gt;requestBody&lt;/code&gt; always contains something when the form is submitted, even if all checkboxes are unchecked, because of the invisible &lt;i&gt;action&lt;/i&gt; input field in the form."/>				</outline>			<outline text="If we are indeed addressing a POST request, our first order of business is to organize the content of the submitted form fields neatly in a &lt;code&gt;postargs&lt;/code&gt; table: Radio provides a convenient &lt;code&gt;webserver.parseArgs&lt;/code&gt; function for that very purpose.">				<outline text="To take a better look at the &lt;code&gt;postargs&lt;/code&gt; table, uncomment debug bundle #2."/>				<outline text="The name of all checked INPUT checkboxes will appear in the table."/>				<outline text="All we have to do is set the value of each boolean user preference according to what we find in the &lt;code&gt;postargs&lt;/code&gt; table, saving the user prefs database for good measure when finished."/>				<outline text="Once this is done, we activate or deactivate each driver according to the current value of the relevant user pref.">					<outline text="The &lt;code&gt;displayMode&lt;/code&gt; template variable is a CSS stunt, so that we may show the user that his/her modifications where taken into account."/>					</outline>				<outline text="Finally, when finished setting preferences, we proceed to returning some HTML content to the client's browser, just as we would for an ordinary GET request."/>				</outline>			</outline>		<outline text="From preferences setting, the visit carries on to the &lt;code&gt;enhancedAggregatorSuite.scratchpad.uninstal&lt;/code&gt; script.">			<outline text="As we saw when taking a look at &lt;code&gt;enhancedAggregatorSuite.instal&lt;/code&gt;, a copy of &lt;code&gt;uninstal&lt;/code&gt; executes from Radio's scratchpad table every time Radio tries to uninstal a tool."/>			<outline text="This happens a lot more than you'd think:">				<outline text="It happens once for every tool found in the &lt;code&gt;user.tools.databases&lt;/code&gt; table every time Radio &lt;i&gt;starts&lt;/i&gt;, before the Tools framework installs anything: you got that right, even active tools are uninstalled first before being reinstalled during Radio's launch sequence."/>				<outline text="It also happens whenever someone deactivates &lt;i&gt;any&lt;/i&gt; tool from Radio's desktop website Tools page."/>				</outline>			<outline text="The job of the &lt;code&gt;uninstal&lt;/code&gt; script is to make sure that all driver enabling hooks into Radio's data structure are removed. In addition, it must also thoroughly clean up after itself in case the tool is no longer present in Radio's Tools folder: we don't want to leave untidy litter behind, do we?"/>			<outline text="The callback script receives as its only argument the full pathname to the tool's database file. This is enough to compute the tool's name, and what the tool's path name &lt;i&gt;should&lt;/i&gt; be if the tool is &lt;i&gt;active&lt;/i&gt;."/>			<outline text="First thing: make sure we are processing something for &lt;i&gt;enhancedAggregator&lt;/i&gt; uninstallation, if not, quickly make our exit."/>			<outline text="Second, provided we are indeed uninstalling enhancedAggregator, remove all hooks to the drivers and callbacks from Radio.root, terminating support for all formats and modules."/>			<outline text="Third, delete our tool's table in temp, and kill our tool's background thread, the one started in &lt;code&gt;enhancedAggregatorThread.script&lt;/code&gt;, which is busily doing nothing at this time.">				<outline text="We retrieve our background threads id from the aptly named &lt;code&gt;temp.scheduler.idThreads&lt;/code&gt; table."/>				<outline text="Since the tool has been uninstalled, the scheduler won't try to restart it every minute."/>				</outline>			<outline text="At this point, we've done enough to restart support for formats and modules in the exact state they were operating before the minute someone reactivates the tool, by clicking the now empty enhancedAggregator checkbox in Radio's desktop website tools page for instance."/>			<outline text="But what if our user has removed the enhancedAggregator.root file from Radio's Tools folder, being now completely fed up with RSS modules and exotic syndication formats ?">				<outline text="We are still executing the &lt;code&gt;uninstal&lt;/code&gt; script, since we are operating from the scratchpad table in Radio root."/>				</outline>			<outline text="We want to remove &lt;i&gt;any trace&lt;/i&gt; of our tool's former presence in Radio's installation.">				<outline text="1/ remove the tool's preferences table from aggregatorData.root."/>				<outline text="2/ remove the tool's update table from Radio.root."/>				<outline text="3/ remove the tool's uninstal callback, we won't need it any more."/>				<outline text="4/ remove the tool's entry from the tools database in Radio.root."/>				</outline>			<outline text="Finally, we commit an elegant variant of sepuku, launching a self distruct order for the uninstal script itself from a separate thread: this is the perfect elimination, no evidence left behind :-)"/>			</outline>		<outline text="Wait, we've ambled from launch to installation to preferences setting to uninstallation, and we still haven't caught a glimpse of what's actually performed by the tool's payload, the core driver activity!">			<outline text="Well, that's where visitors more interested in Usertalk tool building than syndication formats support can walk off and resume normal activities :-)"/>			<outline text="Others are welcome to the - at present - 3 format named tables in &lt;code&gt;enhancedAggregatorSuite&lt;/code&gt;.">				<outline text="That is if they're not already bored to death :-)"/>				</outline>			<outline text="Each tool table has a dedicated &lt;code&gt;instal&lt;/code&gt; script, and a driver subtable with format specific elements."/>			</outline>		<outline text="The &lt;code&gt;enhancedAggregatorSuite.atom&lt;/code&gt; table provides support for Atom feeds aggregation.">			<outline text="That is it &lt;i&gt;will&lt;/i&gt; provide support when a volunteer adds some flesh to the current skeleton."/>			<outline text="The &lt;code&gt;atom.instal&lt;/code&gt; inserts or removes a copy of the &lt;code&gt;atom.formatDriver&lt;/code&gt; table inside the &lt;code&gt;user.xml.rss.formatDrivers&lt;/code&gt; table in Radio.root."/>			<outline text="The name of the entry must be the name of the top XML element for that format, ie &lt;i&gt;feed&lt;/i&gt; for Atom, according to its 0.3 &lt;a href=&quot;http://www.atomenabled.org/developers/syndication/atom-format-spec.php&quot; target=&quot;_blank&quot;&gt;specification&lt;/a&gt;."/>			<outline text="All formatDriver tables must contain a &lt;code&gt;compile&lt;/code&gt; script. The current version for enhancedAggregator doesn't to anything useful: it merely starts filling the service's compilation table with channelTitle and channelLink elements, which are needed for the proper display of Radio's desktop website Subscriptions page."/>			<outline text="A complete implementation would flesh out the compilation table with as many ENT elements as possible while exploring the service's &lt;code&gt;xmlstruct&lt;/code&gt; table, then provide a &lt;code&gt;storyArrived&lt;/code&gt; callback script to add compiled elements to the &lt;code&gt;aggregatorData.stories&lt;/code&gt; table in a manner consistent with what is needed for the proper display of Radio's desktop website News page."/>			</outline>		<outline text="The &lt;code&gt;enhancedAggregatorSuite.ent&lt;/code&gt; table fully supports ENT 1.0 topics.">			<outline text="It's the only complete driver table available at this time."/>			<outline text="&lt;code&gt;ent.instal&lt;/code&gt; inserts or removes the address of the &lt;code&gt;ent.moduleDriver&lt;/code&gt; table inside the &lt;code&gt;user.xml.rss.moduleDrivers&lt;/code&gt; table in Radio.root."/>			<outline text="The name of the entry must be the URI for the RSS module's namespace, in our case &lt;i&gt;http://www.purl.org/NET/ENT/1.0/&lt;/i&gt;.">				<outline text="For additional information on RSS modules driver architecture, check out Jake's &lt;a href=&quot;http://frontier.userland.com/rss2ModuleSupportForXmlAggregator&quot; target=&quot;_blank&quot;&gt;description&lt;/a&gt; on Userland's site."/>				</outline>			<outline text="The only ENT sub-element of &lt;i&gt;item&lt;/i&gt; we are interested in is &lt;i&gt;cloud&lt;/i&gt;, which encapsules a list of available topics for each item. The driver is henced implemented in a single script: &lt;code&gt;ent.moduleDriver.subElementOfItem.cloud&lt;/code&gt;."/>			<outline text="The &lt;code&gt;cloud&lt;/code&gt; script's job is to scan the service's &lt;code&gt;xmlstruct&lt;/code&gt; subtable, starting at the &lt;code&gt;cloud&lt;/code&gt; subtable pointed to by the &lt;code&gt;adrElement&lt;/code&gt; parameter, looking for &lt;code&gt;topic&lt;/code&gt; entries and compiling them into the service's &lt;code&gt;compilation&lt;/code&gt; subtable, pointed to by the &lt;code&gt;adrItem&lt;/code&gt; parameter.">				<outline text="You can take a look at the whole service table, designated by the &lt;code&gt;adrService&lt;/code&gt; parameter, when uncommenting the debug bundle before the main scanning loop."/>				<outline text="I chose to compile topics using the following structure in the &lt;code&gt;item&lt;/code&gt; subtable of the &lt;code&gt;compilation&lt;/code&gt; table:">					<outline text="An encapsulating &lt;code&gt;topics&lt;/code&gt; subtable if topics exist for this item."/>					<outline text="One table entry in &lt;code&gt;topics&lt;/code&gt; for each topic id found."/>					<outline text="Each &lt;code&gt;topics.[id]&lt;/code&gt; subtable holds any number of the three attributes for an ENT topic: its name, href and/or classification."/>					</outline>				</outline>			<outline text="Once topics are compiled by the module driver, they are ready to be inserted into the aggregator's data structure by the &lt;code&gt;storyArrived&lt;/code&gt; callback script. &lt;i&gt;But here's the rub&lt;/i&gt;: with the current version of Radio, the callback script is called and executed &lt;i&gt;before&lt;/i&gt; the module driver gets a chance to compile the module's payload.">				<outline text="Until Userland releases a fix, you can manually open the &lt;code&gt;system.verbs.builtins.xml.rss.compileService&lt;/code&gt; script, and scroll down to the &lt;i&gt;get the items&lt;/i&gt; bundle inside the &lt;i&gt;RSS&lt;/i&gt; branch of the &lt;code&gt;case format&lt;/code&gt; statement."/>				<outline text="Move the &lt;code&gt;runModules (item, adrRss, &quot;&quot;, adritem)&lt;/code&gt; statement you find at the very bottom of the &lt;code&gt;for&lt;/code&gt; loop 2 lines up, so that it is executed just before calling the &lt;code&gt;addHistory&lt;/code&gt; function.">					<outline text="The addHistory function triggers the storyArrived callback sequence."/>					</outline>				<outline text="Compile and save the modified script."/>				</outline>			<outline text="The &lt;code&gt;ent.callbacks.aggregator.storyArrived&lt;/code&gt; script first makes a copy of the compiled topics table, identified by the &lt;code&gt;adrItem&lt;/code&gt; parameter, in the proper story entry of the aggregator's table.">				<outline text="The aggregator's table is &lt;code&gt;aggregatorData.stories&lt;/code&gt;. Entries are designated by their story number. There is a persistent stories counter available as &lt;code&gt;aggregatorData.prefs.nextStoryNum&lt;/code&gt;.">					<outline text="As its name shows, at the time of the execution of &lt;code&gt;storyArrived&lt;/code&gt;, the counter is already designating the next entry, so we must substract one its value to compute our story's id."/>					</outline>				</outline>			<outline text="If the &lt;code&gt;displayTopics&lt;/code&gt; user preference is enabled, the script then modifies the &lt;code&gt;storytext&lt;/code&gt; element of the story's table by appending a string with an HTML rendering of the topics list.">				<outline text="topics' &lt;i&gt;href&lt;/i&gt; attributes are rendered as HTML links around their topic's name."/>				</outline>			</outline>		<outline text="The &lt;code&gt;enhancedAggregatorSuite.esf&lt;/code&gt; table will provide ESF events aggregation some day.">			<outline text="For now it's a hollow shell: the &lt;code&gt;esf.instal&lt;/code&gt; script activates or deactivates the module according to the current user preference, and there are as many scripts in the &lt;code&gt;esf.moduleDriver.subElementOfItem&lt;/code&gt; table as there are &lt;i&gt;required&lt;/i&gt; elements in the ESF &lt;a href=&quot;http://www.esfstandard.org/esfspecification0.9draft.html&quot; target=&quot;_blank&quot;&gt;specification&lt;/a&gt;, but they are all empty."/>			<outline text="Hopefully, someone at &lt;a href=&quot;http://www.evectors.com/&quot; target=&quot;_blank&quot;&gt;eVectors&lt;/a&gt; will soon provide some meaningful content."/>			</outline>		<outline text="I hope that publishing this tour as an outline has made it easier to follow. For more information about outline publishing, check out the &lt;a href=&quot;http://activerenderer.com/&quot; &gt;activeRenderer&lt;/a&gt; site."/>		</body>	</opml>