// SpryDOMUtils.js - version 0.7 - Spry Pre-Release 1.6.1
//
// Copyright (c) 2007. Adobe Systems Incorporated.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of Adobe Systems Incorporated nor the names of its
//     contributors may be used to endorse or promote products derived from this
//     software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

var Spry; if (!Spry) Spry = {}; if (!Spry.Utils) Spry.Utils = {};

//////////////////////////////////////////////////////////////////////
//
// Define Prototype's $() convenience function, but make sure it is
// namespaced under Spry so that we avoid collisions with other
// toolkits.
//
//////////////////////////////////////////////////////////////////////

Spry.$ = function(element)
{
    if (arguments.length > 1)
    {
        for (var i = 0, elements = [], length = arguments.length; i < length; i++)
            elements.push(Spry.$(arguments[i]));
        return elements;
    }
    if (typeof element == 'string')
        element = document.getElementById(element);
    return element;
};

//////////////////////////////////////////////////////////////////////
//
// DOM Utils
//
//////////////////////////////////////////////////////////////////////

Spry.Utils.getAttribute = function(ele, name)
{
    ele = Spry.$(ele);
    if (!ele || !name)
        return null;

    // We need to wrap getAttribute with a try/catch because IE will throw
    // an exception if you call it with a namespace prefixed attribute name
    // that doesn't exist.

    try { var value = ele.getAttribute(name); }
    catch (e) { value == undefined; }

    // XXX: Workaround for Safari 2.x and earlier:
    //
    // If value is undefined, the attribute didn't exist. Check to see if this is
    // a namespace prefixed attribute name. If it is, remove the ':' from the name
    // and try again. This allows us to support spry attributes of the form
    // "spry:region" and "spryregion".

    if (value == undefined && name.search(/:/) != -1)
    {
        try { var value = ele.getAttribute(name.replace(/:/, "")); }
        catch (e) { value == undefined; }
    }

    return value;
};

Spry.Utils.setAttribute = function(ele, name, value)
{
    ele = Spry.$(ele);
    if (!ele || !name)
        return;

    // IE doesn't allow you to set the "class" attribute. You
    // have to set the className property instead.

    if (name == "class")
        ele.className = value;
    else
    {
        // I'm probably being a bit paranoid, but given the fact that
        // getAttribute() throws exceptions when dealing with namespace
        // prefixed attributes, I'm going to wrap this setAttribute()
        // call with try/catch just in case ...

        try { ele.setAttribute(name, value); } catch(e) {}

        // XXX: Workaround for Safari 2.x and earlier:
        //
        // If this is a namespace prefixed attribute, check to make
        // sure an attribute was created. This is necessary because some
        // older versions of Safari (2.x and earlier) drop the namespace
        // prefixes. If the attribute was munged, try removing the ':'
        // character from the attribute name and setting the attribute
        // using the resulting name. The idea here is that even if we
        // remove the ':' character, Spry.Utils.getAttribute() will still
        // find the attribute.

        if (name.search(/:/) != -1 && ele.getAttribute(name) == undefined)
            ele.setAttribute(name.replace(/:/, ""), value);
    }
};

Spry.Utils.removeAttribute = function(ele, name)
{
    ele = Spry.$(ele);
    if (!ele || !name)
        return;

    try { ele.removeAttribute(name); } catch(e) {}

    // XXX: Workaround for Safari 2.x and earlier:
    //
    // If this is a namespace prefixed attribute, make sure we
    // also remove any attributes with the same name, but without
    // the ':' character.

    if (name.search(/:/) != -1)
        ele.removeAttribute(name.replace(/:/, ""));

    // XXX: Workaround for IE
    //
    // IE doesn't allow you to remove the "class" attribute.
    // It requires you to remove "className" instead, so go
    // ahead and try to remove that too.

    if (name == "class")
        ele.removeAttribute("className");
};

Spry.Utils.addClassName = function(ele, className)
{
    ele = Spry.$(ele);
    if (!ele || !className || (ele.className && ele.className.search(new RegExp("\\b" + className + "\\b")) != -1))
        return;
    ele.className += (ele.className ? " " : "") + className;
};

Spry.Utils.removeClassName = function(ele, className)
{
    ele = Spry.$(ele);
    if (Spry.Utils.hasClassName(ele, className))
        ele.className = ele.className.replace(new RegExp("\\s*\\b" + className + "\\b", "g"), "");
};

Spry.Utils.toggleClassName = function(ele, className)
{
    if (Spry.Utils.hasClassName(ele, className))
        Spry.Utils.removeClassName(ele, className);
    else
        Spry.Utils.addClassName(ele, className);
};

Spry.Utils.hasClassName = function(ele, className)
{
    ele = Spry.$(ele);
    if (!ele || !className || !ele.className || ele.className.search(new RegExp("\\b" + className + "\\b")) == -1)
        return false;
    return true;
};

Spry.Utils.camelizeString = function(str)
{
    var cStr = "";
    var a = str.split("-");
    for (var i = 0; i < a.length; i++)
    {
        var s = a[i];
        if (s)
            cStr = cStr ? (cStr + s.charAt(0).toUpperCase() + s.substring(1)) : s;
    }
    return cStr;
};

Spry.Utils.styleStringToObject = function(styleStr)
{
    var o = {};
    if (styleStr)
    {
        pvA = styleStr.split(";");
        for (var i = 0; i < pvA.length; i++)
        {
            var pv = pvA[i];
            if (pv && pv.indexOf(":") != -1)
            {
                var nvA = pv.split(":");
                var n = nvA[0].replace(/^\s*|\s*$/g, "");            
                var v = nvA[1].replace(/^\s*|\s*$/g, "");
                if (n && v)
                    o[Spry.Utils.camelizeString(n)] = v;
            }
        }
    }
    return o;
};

Spry.Utils.addEventListener = function(element, eventType, handler, capture)
{
    try
    {
        if (!Spry.Utils.eventListenerIsBoundToElement(element, eventType, handler, capture))
        {
            element = Spry.$(element);
            handler = Spry.Utils.bindEventListenerToElement(element, eventType, handler, capture);
            if (element.addEventListener)
                element.addEventListener(eventType, handler, capture);
            else if (element.attachEvent)
                element.attachEvent("on" + eventType, handler);
        }
    }
    catch (e) {}
};

Spry.Utils.removeEventListener = function(element, eventType, handler, capture)
{
    try
    {
            element = Spry.$(element);
            handler = Spry.Utils.unbindEventListenerFromElement(element, eventType, handler, capture);
            if (element.removeEventListener)
                element.removeEventListener(eventType, handler, capture);
            else if (element.detachEvent)
                element.detachEvent("on" + eventType, handler);
    }
    catch (e) {}
};

Spry.Utils.eventListenerHash = {};
Spry.Utils.nextEventListenerID = 1;

Spry.Utils.getHashForElementAndHandler = function(element, eventType, handler, capture)
{
    var hash = null;
    element = Spry.$(element);
    if (element)
    {
        if (typeof element.spryEventListenerID == "undefined")
            element.spryEventListenerID = "e" + (Spry.Utils.nextEventListenerID++);
        if (typeof handler.spryEventHandlerID == "undefined")
            handler.spryEventHandlerID = "h" + (Spry.Utils.nextEventListenerID++);    
        hash = element.spryEventListenerID + "-" + handler.spryEventHandlerID + "-" + eventType + (capture?"-capture":"");
    }
    return hash;
};

Spry.Utils.eventListenerIsBoundToElement = function(element, eventType, handler, capture)
{
    element = Spry.$(element);
    var hash = Spry.Utils.getHashForElementAndHandler(element, eventType, handler, capture);
    return Spry.Utils.eventListenerHash[hash] != undefined;
};

Spry.Utils.bindEventListenerToElement = function(element, eventType, handler, capture)
{
    element = Spry.$(element);
    var hash = Spry.Utils.getHashForElementAndHandler(element, eventType, handler, capture);
    if (Spry.Utils.eventListenerHash[hash])
        return Spry.Utils.eventListenerHash[hash];
    return Spry.Utils.eventListenerHash[hash] = function(e)
    {
        e = e || window.event;

        if (!e.preventDefault) e.preventDefault = function() { this.returnValue = false; };
        if (!e.stopPropagation) e.stopPropagation = function() { this.cancelBubble = true; };

        var result = handler.call(element, e);
        if (result == false)
        {
            e.preventDefault();
            e.stopPropagation();
        }
        return result;
    };
};

Spry.Utils.unbindEventListenerFromElement = function(element, eventType, handler, capture)
{
    element = Spry.$(element);
    var hash = Spry.Utils.getHashForElementAndHandler(element, eventType, handler, capture);
    if (Spry.Utils.eventListenerHash[hash])
    {
        handler = Spry.Utils.eventListenerHash[hash];
        Spry.Utils.eventListenerHash[hash] = undefined;
    }
    return handler;
};

Spry.Utils.addLoadListener = function(handler)
{
    if (typeof window.addEventListener != 'undefined')
        window.addEventListener('load', handler, false);
    else if (typeof document.addEventListener != 'undefined')
        document.addEventListener('load', handler, false);
    else if (typeof window.attachEvent != 'undefined')
        window.attachEvent('onload', handler);
};

Spry.Utils.getAncestor = function(ele, selector)
{
    ele = Spry.$(ele);
    if (ele)
    {
        var s = Spry.$$.tokenizeSequence(selector ? selector : "*")[0];
        var t = s ? s[0] : null;
        if (t)
        {
            var p = ele.parentNode;
            while (p)
            {
                if (t.match(p))
                    return p;
                p = p.parentNode;
            }
        }
    }
    return null;
};

//////////////////////////////////////////////////////////////////////
//
// CSS Selector Matching
//
//////////////////////////////////////////////////////////////////////

Spry.$$ = function(selectorSequence, rootNode)
{
    if (!rootNode)
        rootNode = document;
    else
        rootNode = Spry.$(rootNode);

    var sequences = Spry.$$.tokenizeSequence(selectorSequence);

    var matches = [];
    Spry.$$.addExtensions(matches);
    ++Spry.$$.queryID;

    var nid = 0;
    var ns = sequences.length;
    for (var i = 0; i < ns; i++)
    {
        var m = Spry.$$.processTokens(sequences[i], rootNode);
        var nm = m.length;
        for (var j = 0; j < nm; j++)
        {
            var n = m[j];
            if (!n.spry$$ID)
            {
                n.spry$$ID = ++nid;
                matches.push(n);
            }
        }
    }

    var nm = matches.length;
    for (i = 0; i < nm; i++)
        matches[i].spry$$ID = undefined;

    return matches;
};

Spry.$$.cache = {};
Spry.$$.queryID = 0;

Spry.$$.Token = function()
{
    this.type = Spry.$$.Token.SELECTOR;
    this.name = "*";
    this.id = "";
    this.classes = [];
    this.attrs = [];
    this.pseudos = [];
};

Spry.$$.Token.Attr = function(n, v)
{
    this.name = n;
    this.value = v ? new RegExp(v) : undefined;
};

Spry.$$.Token.PseudoClass = function(pstr)
{
    this.name = pstr.replace(/\(.*/, "");
    this.arg = pstr.replace(/^[^\(\)]*\(?\s*|\)\s*$/g, "");
    this.func = Spry.$$.pseudoFuncs[this.name];
};

Spry.$$.Token.SELECTOR = 0;
Spry.$$.Token.COMBINATOR = 1;

Spry.$$.Token.prototype.match = function(ele, nameAlreadyMatches)
{
    if (this.type == Spry.$$.Token.COMBINATOR)
        return false;
    if (!nameAlreadyMatches && this.name != '*' && this.name != ele.nodeName.toLowerCase())
        return false;
    if (this.id && this.id != ele.id)
        return false;
    var classes = this.classes;
    var len = classes.length;
    for (var i = 0; i < len; i++)
    {
        if (!ele.className || !classes[i].value.test(ele.className))
            return false;
    }

    var attrs = this.attrs;
    len = attrs.length;
    for (var i = 0; i < len; i++)
    {
        var a = attrs[i];
        var an = ele.attributes.getNamedItem(a.name);
        if (!an || (!a.value && an.nodeValue == undefined) || (a.value && !a.value.test(an.nodeValue)))
            return false;
    }

    var ps = this.pseudos;
    var len = ps.length;
    for (var i = 0; i < len; i++)
    {
        var p = ps[i];
        if (p && p.func && !p.func(p.arg, ele, this))
            return false;
    }

    return true;
};

Spry.$$.Token.prototype.getNodeNameIfTypeMatches = function(ele)
{
    var nodeName = ele.nodeName.toLowerCase();
    if (this.name != '*')
    {
        if (this.name != nodeName)
            return null;
        return this.name;
    }
    return nodeName;
};

Spry.$$.escapeRegExpCharsRE = /\/|\.|\*|\+|\(|\)|\[|\]|\{|\}|\\|\|/g;

Spry.$$.tokenizeSequence = function(s)
{
    var cc = Spry.$$.cache[s];
    if (cc) return cc;

    // Attribute Selector: /(\[[^\"'~\^\$\*\|\]=]+([~\^\$\*\|]?=\s*('[^']*'|"[^"]*"|[^"'\]]+))?\s*\])/g
    // Simple Selector:    /((:[^\.#:\s,>~\+\[\]]+\(([^\(\)]+|\([^\(\)]*\))*\))|[\.#:]?[^\.#:\s,>~\+\[\]]+)/g
    // Combinator:         /(\s*[\s,>~\+]\s*)/g

    var tokenExpr = /(\[[^\"'~\^\$\*\|\]=]+([~\^\$\*\|]?=\s*('[^']*'|"[^"]*"|[^"'\]]+))?\s*\])|((:[^\.#:\s,>~\+\[\]]+\(([^\(\)]+|\([^\(\)]*\))*\))|[\.#:]?[^\.#:\s,>~\+\[\]]+)|(\s*[\s,>~\+]\s*)/g;

    var tkn = new Spry.$$.Token;
    var sequence = [];
    sequence.push(tkn);
    var tokenSequences = [];
    tokenSequences.push(sequence);

    s = s.replace(/^\s*|\s*$/, "");

    var expMatch = tokenExpr.exec(s);
    while (expMatch)
    {
        var tstr = expMatch[0];
        var c = tstr.charAt(0);
        switch (c)
        {
            case '.':
                tkn.classes.push(new Spry.$$.Token.Attr("class", "\\b" + tstr.substr(1) + "\\b"));
                break;
            case '#':
                tkn.id = tstr.substr(1);
                break;
            case ':':
                tkn.pseudos.push(new Spry.$$.Token.PseudoClass(tstr));
                break;
            case '[':
                var attrComps = tstr.match(/\[([^\"'~\^\$\*\|\]=]+)(([~\^\$\*\|]?=)\s*('[^']*'|"[^"]*"|[^"'\]]+))?\s*\]/);
                var name = attrComps[1];                
                var matchType = attrComps[3];
                var val = attrComps[4];
                if (val)
                {
                    val = val.replace(/^['"]|['"]$/g, "");
                    val = val.replace(Spry.$$.escapeRegExpCharsRE, '\\$&');
                }

                var matchStr = undefined;

                switch(matchType)
                {
                    case "=":
                        matchStr = "^" + val + "$";
                        break;
                    case "^=":
                        matchStr = "^" + val;
                        break;
                    case "$=":
                        matchStr = val + "$";
                        break;
                    case "~=":
                    case "|=":
                        matchStr = "\\b" + val + "\\b";
                        break;
                    case "*=":
                        matchStr = val;
                        break;
                }

                tkn.attrs.push(new Spry.$$.Token.Attr(name, matchStr));
                break;
            default:
                var combiMatch = tstr.match(/^\s*([\s,~>\+])\s*$/);
                if (combiMatch)
                {
                    if (combiMatch[1] == ',')
                    {
                        sequence = new Array;
                        tokenSequences.push(sequence);
                        tkn = new Spry.$$.Token;
                        sequence.push(tkn);
                    }
                    else
                    {
                        tkn = new Spry.$$.Token;
                        tkn.type = Spry.$$.Token.COMBINATOR;
                        tkn.name = combiMatch[1];
                        sequence.push(tkn);
                        tkn = new Spry.$$.Token();
                        sequence.push(tkn);
                    }
                }
                else
                    tkn.name = tstr.toLowerCase();
                break;
        }
        expMatch = tokenExpr.exec(s);
    }

    Spry.$$.cache[s] = tokenSequences;

    return tokenSequences;
};

Spry.$$.combinatorFuncs = {
    // Element Descendant

    " ": function(nodes, token)
    {
        var uid = ++Spry.$$.uniqueID;
        var results = [];
        var nn = nodes.length;
        for (var i = 0; i < nn; i++)
        {
            var n = nodes[i];
            if (uid != n.spry$$uid)
            {
                // n.spry$$uid = uid;
                var ea = nodes[i].getElementsByTagName(token.name);
                var ne = ea.length;
                for (var j = 0; j < ne; j++)
                {
                    var e = ea[j];
                    if (token.match(e, true))
                        results.push(e);
                    e.spry$$uid = uid;
                }
            }
        }
        return results;
    },

    // Element Child

    ">": function(nodes, token)
    {
        var results = [];
        var nn = nodes.length;
        for (var i = 0; i < nn; i++)
        {
            var n = nodes[i].firstChild;
            while (n)
            {
                if (n.nodeType == 1 /* Node.ELEMENT_NODE */ && token.match(n))
                    results.push(n);
                n = n.nextSibling;
            }
        }
        return results;
    },

    // Element Immediately Preceded By

    "+": function(nodes, token)
    {
        var results = [];
        var nn = nodes.length;
        for (var i = 0; i < nn; i++)
        {
            var n = nodes[i].nextSibling;
            while (n && n.nodeType != 1 /* Node.ELEMENT_NODE */)
                n = n.nextSibling;
            if (n && token.match(n))
                results.push(n);
        }
        return results;
    },

    // Element Preceded By

    "~": function(nodes, token)
    {
        var uid = ++Spry.$$.uniqueID;
        var results = [];
        var nn = nodes.length;
        for (var i = 0; i < nn; i++)
        {
            var n = nodes[i].nextSibling;
            while (n)
            {
                if (n.nodeType == 1 /* Node.ELEMENT_NODE */)
                {
                    if (uid == n.spry$$uid)
                        break;

                    if (token.match(n))
                    {
                        results.push(n);
                        n.spry$$uid = uid;
                    }
                }
                n = n.nextSibling;
            }
        }
        return results;
    }
};

Spry.$$.uniqueID = 0;

Spry.$$.pseudoFuncs = {
    ":first-child": function(arg, node, token)
    {
        var n = node.previousSibling;
        while (n)
        {
            if (n.nodeType == 1) return false; // Node.ELEMENT_NODE
            n = n.previousSibling;
        }

        return true;
    },

    ":last-child": function(arg, node, token)
    {
        var n = node.nextSibling;
        while (n)
        {
            if (n.nodeType == 1) // Node.ELEMENT_NODE
                return false;
            n = n.nextSibling;
        }
        return true;
    },

    ":empty": function(arg, node, token)
    {
        var n = node.firstChild;
        while (n)
        {
            switch(n.nodeType)
            {
                case 1: // Node.ELEMENT_NODE
                case 3: // Node.TEXT_NODE
                case 4: // Node.CDATA_NODE
                case 5: // Node.ENTITY_REFERENCE_NODE
                    return false;
            }
            n = n.nextSibling;
        }
        return true;
    },

    ":nth-child": function(arg, node, token)
    {
        return Spry.$$.nthChild(arg, node, token);
    },

    ":nth-last-child": function(arg, node, token)
    {
        return Spry.$$.nthChild(arg, node, token, true);
    },

    ":nth-of-type": function(arg, node, token)
    {
        return Spry.$$.nthChild(arg, node, token, false, true);
    },
    
    ":nth-last-of-type": function(arg, node, token)
    {
        return Spry.$$.nthChild(arg, node, token, true, true);
    },
    
    ":first-of-type": function(arg, node, token)
    {
        var nodeName = token.getNodeNameIfTypeMatches(node);
        if (!nodeName) return false;

        var n = node.previousSibling;
        while (n)
        {
            if (n.nodeType == 1 && nodeName == n.nodeName.toLowerCase()) return false; // Node.ELEMENT_NODE
            n = n.previousSibling;
        }

        return true;
    },

    ":last-of-type": function(arg, node, token)
    {
        var nodeName = token.getNodeNameIfTypeMatches(node);
        if (!nodeName) return false;

        var n = node.nextSibling;
        while (n)
        {
            if (n.nodeType == 1 && nodeName == n.nodeName.toLowerCase()) // Node.ELEMENT_NODE
                return false;
            n = n.nextSibling;
        }
        return true;
    },

    ":only-child": function(arg, node, token)
    {
        var f = Spry.$$.pseudoFuncs;
        return f[":first-child"](arg, node, token) && f[":last-child"](arg, node, token);
    },

    ":only-of-type": function(arg, node, token)
    {
        var f = Spry.$$.pseudoFuncs;
        return f[":first-of-type"](arg, node, token) && f[":last-of-type"](arg, node, token);
    },

    ":not": function(arg, node, token)
    {
        var s = Spry.$$.tokenizeSequence(arg)[0];
        var t = s ? s[0] : null;
        return !t || !t.match(node);
    },

    ":enabled": function(arg, node, token)
    {
        return !node.disabled;
    },

    ":disabled": function(arg, node, token)
    {
        return node.disabled;
    },

    ":checked": function(arg, node, token)
    {
        return node.checked;
    },

    ":root": function(arg, node, token)
    {
        return node.parentNode && node.ownerDocument && node.parentNode == node.ownerDocument;
    }
};

Spry.$$.nthRegExp = /((-|[0-9]+)?n)?([+-]?[0-9]*)/;

Spry.$$.nthCache = {
      "even": { a: 2, b: 0, mode: 1, invalid: false }
    , "odd":  { a: 2, b: 1, mode: 1, invalid: false }
    , "2n":   { a: 2, b: 0, mode: 1, invalid: false }
    , "2n+1": { a: 2, b: 1, mode: 1, invalid: false }
};

Spry.$$.parseNthChildString = function(str)
{
    var o = Spry.$$.nthCache[str];
    if (!o)
    {
        var m = str.match(Spry.$$.nthRegExp);
        var n = m[1];
        var a = m[2];
        var b = m[3];

        if (!a)
        {
            // An 'a' value was not specified. Was there an 'n' present?
            // If so, we treat it as an increment of 1, otherwise we're
            // in no-repeat mode.

            a = n ? 1 : 0;
        }
        else if (a == "-")
        {
            // The string is using the "-n" short-hand which is
            // short for -1.

            a = -1;
        }
        else
        {
            // An integer repeat value for 'a' was specified. Convert
            // it into number.

            a = parseInt(a, 10);
        }

        // If a 'b' value was specified, turn it into a number.
        // If no 'b' value was specified, default to zero.

        b = b ? parseInt(b, 10) : 0;

        // Figure out the mode:
        //
        // -1 - repeat backwards
        //  0 - no repeat
        //  1 - repeat forwards

        var mode = (a == 0) ? 0 : ((a > 0) ? 1 : -1);
        var invalid = false;

        // Fix up 'a' and 'b' for proper repeating.

        if (a > 0 && b < 0)
        {
            b = b % a;
            b = ((b=(b%a)) < 0) ? a + b : b;
        }
        else if (a < 0)
        {
            if (b < 0)
                invalid = true;
            else
                a = Math.abs(a);
        }

        o = new Object;
        o.a = a;
        o.b = b;
        o.mode = mode;
        o.invalid = invalid;

        Spry.$$.nthCache[str] = o;
    }

    return o;
};

Spry.$$.nthChild = function(arg, node, token, fromLastSib, matchNodeName)
{
    if (matchNodeName)
    {
        var nodeName = token.getNodeNameIfTypeMatches(node);
        if (!nodeName) return false;
    }

    var o = Spry.$$.parseNthChildString(arg);

    if (o.invalid)
        return false;

    var qidProp = "spry$$ncQueryID";
    var posProp = "spry$$ncPos";
    var countProp = "spry$$ncCount";
    if (matchNodeName)
    {
        qidProp += nodeName;
        posProp += nodeName;
        countProp += nodeName;
    }

    var parent = node.parentNode;
    if (parent[qidProp] != Spry.$$.queryID)
    {
        var pos = 0;
        parent[qidProp] = Spry.$$.queryID;
        var c = parent.firstChild;
        while (c)
        {
            if (c.nodeType == 1 && (!matchNodeName || nodeName == c.nodeName.toLowerCase()))
                c[posProp] = ++pos;
            c = c.nextSibling;
        }
        parent[countProp] = pos;
    }

    pos = node[posProp];
    if (fromLastSib)
        pos = parent[countProp] - pos + 1;

/*
    var sib = fromLastSib ? "nextSibling" : "previousSibling";

    var pos = 1;
    var n = node[sib];
    while (n)
    {
        if (n.nodeType == 1 && (!matchNodeName || nodeName == n.nodeName.toLowerCase()))
        {
            if (n == node) break;
            ++pos;
        }
        n = n[sib];
    }
*/

    if (o.mode == 0) // Exact match
        return pos == o.b;
    if (o.mode > 0) // Forward Repeat
        return (pos < o.b) ? false : (!((pos - o.b) % o.a));
    return (pos > o.b) ? false : (!((o.b - pos) % o.a)); // Backward Repeat
};

Spry.$$.processTokens = function(tokens, root)
{
    var numTokens = tokens.length;
    var nodeSet = [ root ];
    var combiFunc = null;

    for (var i = 0; i < numTokens && nodeSet.length > 0; i++)
    {
        var t = tokens[i];
        if (t.type == Spry.$$.Token.SELECTOR)
        {
            if (combiFunc)
            {
                nodeSet = combiFunc(nodeSet, t);
                combiFunc = null;
            }
            else
                nodeSet = Spry.$$.getMatchingElements(nodeSet, t);
        }
        else // Spry.$$.Token.COMBINATOR
            combiFunc = Spry.$$.combinatorFuncs[t.name];
    }
    return nodeSet;
};

Spry.$$.getMatchingElements = function(nodes, token)
{
    var results = [];
    if (token.id)
    {
        n = nodes[0];
        if (n && n.ownerDocument)
        {
            var e = n.ownerDocument.getElementById(token.id);
            if (e)
            {
                // XXX: We need to make sure that the element
                //      we found is actually underneath the root
                //      we were given!

                if (token.match(e))
                    results.push(e);
            }
            return results;
        }
    }

    var nn = nodes.length;
    for (var i = 0; i < nn; i++)
    {
        var n = nodes[i];
        // if (token.match(n)) results.push(n);
        
        var ea = n.getElementsByTagName(token.name);
        var ne = ea.length;
        for (var j = 0; j < ne; j++)
        {
            var e = ea[j];
            if (token.match(e, true))
                results.push(e);
        }
    }
    return results;
};

/*
Spry.$$.dumpSequences = function(sequences)
{
    Spry.Debug.trace("<hr />Number of Sequences: " + sequences.length);
    for (var i = 0; i < sequences.length; i++)
    {
        var str = "";
        var s = sequences[i];
        Spry.Debug.trace("<hr />Sequence " + i + " -- Tokens: " + s.length);
        for (var j = 0; j < s.length; j++)
        {
            var t = s[j];
            if (t.type == Spry.$$.Token.SELECTOR)
            {
                str += "  SELECTOR:\n    Name: " + t.name + "\n    ID: " + t.id + "\n    Attrs:\n";
                for (var k = 0; k < t.classes.length; k++)
                    str += "      " + t.classes[k].name + ": " + t.classes[k].value + "\n";
                for (var k = 0; k < t.attrs.length; k++)
                    str += "      " + t.attrs[k].name + ": " + t.attrs[k].value + "\n";
                str += "    Pseudos:\n";
                for (var k = 0; k < t.pseudos.length; k++)
                    str += "      " + t.pseudos[k].name + (t.pseudos[k].arg ? "(" + t.pseudos[k].arg + ")" : "") + "\n";
            }
            else
            {
                str += "  COMBINATOR:\n    Name: '" + t.name + "'\n"; 
            }
        }
        Spry.Debug.trace("<pre>" + Spry.Utils.encodeEntities(str) + "</pre>");
    }
};
*/

Spry.$$.addExtensions = function(a)
{
    for (var f in Spry.$$.Results)
        a[f] = Spry.$$.Results[f];
};

Spry.$$.Results = {};

Spry.$$.Results.forEach = function(func)
{
    var n = this.length;
    for (var i = 0; i < n; i++)
        func(this[i]);
    return this;
};

Spry.$$.Results.setAttribute = function(name, value)
{
    return this.forEach(function(n) { Spry.Utils.setAttribute(n, name, value); });
};

Spry.$$.Results.removeAttribute = function(name)
{
    return this.forEach(function(n) { Spry.Utils.removeAttribute(n, name); });
};

Spry.$$.Results.addClassName = function(className)
{
    return this.forEach(function(n) { Spry.Utils.addClassName(n, className); });
};

Spry.$$.Results.removeClassName = function(className)
{
    return this.forEach(function(n) { Spry.Utils.removeClassName(n, className); });
};

Spry.$$.Results.toggleClassName = function(className)
{
    return this.forEach(function(n) { Spry.Utils.toggleClassName(n, className); });
};

Spry.$$.Results.addEventListener = function(eventType, handler, capture, bindHandler)
{
    return this.forEach(function(n) { Spry.Utils.addEventListener(n, eventType, handler, capture, bindHandler); });
};

Spry.$$.Results.removeEventListener = function(eventType, handler, capture)
{
    return this.forEach(function(n) { Spry.Utils.removeEventListener(n, eventType, handler, capture); });
};

Spry.$$.Results.setStyle = function(style)
{
    if (style)
    {
        style = Spry.Utils.styleStringToObject(style);
        this.forEach(function(n)
        {
            for (var p in style)
                try { n.style[p] = style[p]; } catch (e) {}
        });
    }
    return this;
};

Spry.$$.Results.setProperty = function(prop, value)
{
    if (prop)
    {
        if (typeof prop == "string")
        {
            var p = {};
            p[prop] = value;
            prop = p;
        }

        this.forEach(function(n)
        {
            for (var p in prop)
                try { n[p] = prop[p]; } catch (e) {}
        });
    }
    return this;
};

