Monday, November 08, 2010 at 12:06 AM.
system.verbs.builtins.tcp.im.builtinDrivers.jabber.code.thread
on thread ( connection ) {
<<Changes
<<5/14/02; 1:51:34 PM by JB
<<Changelog created.
<<Thread processes the incoming XML stream, breaking apart the XML document's parent elements and calling the handler functions.
<<This function definately assumes that all incoming XML is valid. Invalid XML will kill this dead, which can not be avoided.
<<If anybody is using CDATA with Jabber, this routine may need some work.
local (status, bytes, log, buffer = "", tcpStream = connection^.tcpStream, x, lastNOP = date(0) );
local ( i = 0, chunk, nextTag );
on firstTag ( string ) {
<<extracts the first tag in the string
local ( tag, tagBegin, tagEnd );
tagBegin = string.patternMatch ( "<", string );
tagEnd = string.patternMatch ( ">", string );
if tagEnd == 0 { // tag's not done coming in.
return ""};
tag = string.mid ( string, tagBegin, tagEnd - tagBegin + 1);
return tag};
on getTagName ( string ) {
<<Given a tag, gives the name of the tag, including namespace.
local ( tag, tagBegin, tagEnd );
tagBegin = string.patternMatch ( "<", string );
tagEnd = string.patternMatch ( ">", string );
tag = string.mid ( string, tagBegin + 1, tagEnd - tagBegin - 1 );
if tag contains " " {
tag = string.mid ( tag, 1, string.patternMatch ( " ", tag ) - 1 )};
return tag};
on tagSelfClosed ( tag ) {
<<return true if the tag is self-closing (for example, "<img />"), false otherwise. I'm fairly certain this test works correctly in all cases.... all ">"'s should be encoded as >.
return tag contains "/>"};
on getFirstTag ( strAdr ) {
<<Given the string address, this function returns the first tag, complete with contents, and removes it from the original string. If there's no complete tag, this function does nothing to strAdr, and returns "". This function is the magic bridge between parse-as-you-go and parse-all-at-once.
<<SPECIAL CASE: Because I don't really care about the initial stream:stream tag, this function will automatically *throw it away*. It's dirty here but it really simplifies the main loop.
<<SPECIAL CASE: XML directives are simply and silently tossed out. <? ?> (Did I see somewhere the Jabber server does this too?)
<<ASSUMPTION: I'm pretty sure that no tag is capable of containing itself according to the Jabber spec. Thus, to find the end of any given tag name "TAG", we can look for the *first* "/TAG" and be correct. Start slinging around <iq> tags with <iq> elements, or <message> tags with embedded <message> elements, and the stream will die. This is theoretically possible, but it should be a while before anyone does it. (This is fixable, but requires creating a tag stack, which will make for some ugly parsing and calls to regex.)
//common case: Nothing in the stream
if string.patternMatch ( "<", strAdr^ ) == 0 {
return ""};
// There's a tag. What's its name?
local ( tag, tagName );
tag = firstTag ( strAdr^ );
tagName = getTagName ( tag );
if tagName == "" { // didn't have a whole tag
return ""};
// if we don't like the tag, chuck it.
if tagName == "stream:stream" || tagName == "/stream:stream" || tagName beginsWith "?" {
strAdr^ = string.mid(strAdr^, string.patternMatch(">", strAdr^) + 1, infinity );
return getFirstTag ( strAdr )};
// Otherwise, retrieve the whole tag
if tagSelfClosed ( tag ) {
strAdr^ = string.mid ( strAdr^, string.patternMatch ( "/>", strAdr^) + 2, infinity );
return tag};
tagEnd = string.patternMatch ( "</" + tagName, strAdr^ );
if tagEnd == 0 { // not done coming in yet
return ""};
wholeTag = string.mid ( strAdr^, 1, tagEnd + sizeOf ( tagName ) + 2);
strAdr^ = string.mid ( strAdr^, tagEnd+ sizeOf ( tagName ) + 3, infinity );
return wholeTag};
status = tcp.statusStream ( tcpStream, @bytes );
try {
while ( status == "OPEN" || status == "DATA" ) {
// recieve messages
if status == "DATA" {
chunk = tcp.readStream ( tcpStream, bytes );
chunk = string ( chunk );
buffer = buffer + chunk;
if user.im.jabber.prefs.flDebug {
local ( db = Frontier.openDataFile("jabberLog") );
local ( debugStr = @db^.jabberIn );
if not defined ( debugStr^ ) {
debugStr^ = ""};
if typeOf ( debugStr^ ) != stringType { // allows conversion to wpText on the fly
table.moveAndRename ( debugStr, table.uniqueName ( nameOf ( debugStr^ ), db, 3 ) );
debugStr^ = ""};
debugStr^ = debugStr^ + chunk;
debugStr^ = debugStr^ + "\n"};
nextTag = getFirstTag ( @buffer );
while nextTag != "" {
new ( tableType, @x );
try {
<<local ( context ); new ( tableType, @context )
xml.compile ( nextTag, @x );
<<context.x = x
<<thread.callScript("tcp.im.builtinDrivers.jabber.code.handleMessage", {connection, @x}, @context)
tcp.im.builtinDrivers.jabber.code.handleMessage ( connection, @x )} // non-thread version of thread call
else {
scriptError ( "Can't continue processing Jabber events because " + tryerror)};
nextTag = getFirstTag ( @buffer )}};
try {
bundle { // every minute, send a 'nop XML' (bare linefeed), as suggested by the client developer cheat sheet
if clock.now() > lastNOP + 60 {
try {
tcp.im.builtinDrivers.jabber.code.writeXML ( "\n", connection: connection )}; // if this scriptErrors, then the stream closed and the next loop will catch that
lastNOP = clock.now()}}};
thread.sleepTicks ( user.im.jabber.prefs.streamScanFrequency );
status = tcp.statusStream ( tcpStream, @bytes )}};
if defined (connection^) { // if we've made it down here, the session is over, for one reason or another
tcp.im.builtinDrivers.jabber.code.closeConnection(connection)}};
bundle { //test code
thread (@system.temp.jabber.connection)}
This listing is for code that runs in the OPML Editor environment. I created these listings because I wanted the search engines to index it, so that when I want to look up something in my codebase I don't have to use the much slower search functionality in my object database. Dave Winer.