/**
 *$Id: xslt.js 129 2009-03-18 15:38:46Z aanpilogov $
 *
 */
function CXSLTTransformer()
{
    this.XSLCacheIDX = [];
    this.XSLCacheXML = [];
    this.defaultOpts = {'async' : true, 'output' : 'string', 'callback' : null, 'container':null};
    var cachePointer = 0;
    var maxCacheItems = 20;
    var pf = PuskFramework;
    var me = this;

    function disableOutputEscaping(elem)
    {
        var container, doeyContainers = elem.getElementsByTagName('VAR');
        var toDelete = [];
        var cn = doeyContainers.length;
        for(var i=0; i<cn; i++)
        {
            container = doeyContainers[i];
            if (!pf.elem.hasClass(container, 'doey')) { continue; }
            var d = pf.$$$('div');
            d.innerHTML = pf.str.unescapeHtml(container.innerHTML);
            var p = container.parentNode;
            while(d.firstChild)
            {
                p.insertBefore(d.firstChild, container);
            }
            toDelete.push(container);
        }
        for(var i = 0; i < toDelete.length; i++)
        {
            pf.$_(toDelete[i]);
        }
        return elem;
    };

    function undoey(x)
    {
        var doeys = x.selectNodes('//*[@disable-output-escaping = "yes"]');
        for (var i = 0, l = doeys.length; i < l; i++)
        {
            var dummy = pf.$$$('var');
            dummy.setAttribute('class', 'doey');
            doeys[i].parentNode.insertBefore(dummy, doeys[i]);
            dummy.appendChild(doeys[i]);
        }
    };

    function getResType(res)
    {
        var type = pf.$type(res);
        switch(true)
        {
            case (type == 'object' || type == 'element'): return 'DOM';
            case (type == 'string' && pf.str.startsWith(res, '<?xml', true)): return 'XML';
            case (type == 'string' && res.length > 4): return 'URL';
        }
        return false;
    };

    function getXML(res, scope, callback)
    {
        scope = scope || 'xml';
        var resType = getResType(res);
        var async = (pf.$type(callback) == 'function');
        switch(resType)
        {
            case 'DOM':
            {
                return res;
            }
            case 'XML':
            {
                return pf.xml.parse(res);
            }
            case 'URL':
            {
                if (async)
                {
                    pf.ajax.get({'url': res, 'onSuccess': function(req){return callback(req.responseXML);}, 'generateUniqueUrl' : false});
                    return false;
                }
                else
                {
                    var req = pf.ajax.get({'url': res, 'generateUniqueUrl' : false, 'async' : false});
                    return req.responseXML;
                }
            }
            default:
            {
                throw pf.$exGen('xslt.transform(): typeof(xml) argument is unknown, expected object|element|string');
            }
        }
    };

    function getXSL(xsl, callback)
    {
        var async = (pf.$type(callback) == 'function');
        var resType = getResType(xsl);
        if (resType == 'URL')
        {
            var cacheRec = pf.arr.indexOf(me.XSLCacheIDX, xsl);
            if (me.XSLCacheIDX[cacheRec])
            {
                return me.XSLCacheXML[cacheRec];
            }
        }
        var xslObj = getXML(xsl, 'xsl', async ? _prepare : null);
        if (!async || xslObj) return _prepare(xslObj);

        function _prepare(xslObj)
        {
            if (pf.browsCap.isMozilla)
            {
                undoey(xslObj);
            }

            if (resType == 'URL')
            {
                if (cachePointer >= maxCacheItems)
                {
                    cachePointer = 0;
                    me.XSLCacheXML[cachePointer] = xslObj;
                    me.XSLCacheIDX[cachePointer] = xsl;
                }
                else
                {
                    me.XSLCacheXML.push(xslObj);
                    me.XSLCacheIDX.push(xsl);
                }
                cachePointer++;
            }
            return (async) ? callback(xslObj) : xslObj;
        }
    };

    function getOutputMethod(xsl)
    {
        var tags = xsl.documentElement.childNodes;
        var method = 'html';
        for (var i = 0; i < tags.length; i++)
        {
            if (tags[i].tagName == 'xsl:output')
            {
                method = tags[i].getAttribute('method');
                break;
            }
        }
        return method;
    };

    function doTransformNative(xml, xsl, arg, method, output)
    {
        var oXSLTProcessor = new XSLTProcessor();
        oXSLTProcessor.importStylesheet(xsl);

        for (var prop in arg)
        {
            if (arg[prop] === 0) arg[prop] = '0';
            if (prop != '' && arg[prop] && arg[prop].toString) oXSLTProcessor.setParameter("", prop, arg[prop].toString());
        }

        if (method == 'xml')
        {
            return (output == 'dom') ? oXSLTProcessor.transformToDocument(xml) : pf.xml.serialize(oXSLTProcessor.transformToDocument(xml));
        }

        var d = pf.$$$('div');
        d.appendChild(oXSLTProcessor.transformToFragment(xml, document));
        if (pf.browsCap.isMozilla)
        {
            d = disableOutputEscaping(d);
        }
        d.innerHTML = postProcessJs(d.innerHTML);

        switch(output)
        {
            default:
            case 'string':
            {
                return d.innerHTML;
            }
            case 'dom':
            {
                var f = document.createDocumentFragment();
                while(d.firstChild) f.appendChild(d.firstChild);
                return f;
            }
        }
    };

    function doTransformIE(xml, xsl, arg, method, output)
    {
        var oXSLTTemp = new ActiveXObject("Msxml2.XSLTemplate.3.0");
        var xslDocument = new ActiveXObject("Msxml2.FreeThreadedDOMDocument.3.0");
        xslDocument.validateOnParse = false;
        xslDocument.loadXML(pf.xml.serialize(xsl));
        oXSLTTemp.stylesheet = xslDocument;

        var oXSLTProcessor = oXSLTTemp.createProcessor();
        oXSLTProcessor.input = xml;
        for (prop in arg)
        {
            if (arg[prop] === 0) arg[prop] = '0';
            if (prop != '' && arg[prop] && arg[prop].toString) oXSLTProcessor.addParameter(prop, arg[prop].toString());
        }
        oXSLTProcessor.transform();
        var content = oXSLTProcessor.output;
        if (method == 'xml')
        {
            if(output == 'dom')
            {
                var outDoc = new ActiveXObject("Microsoft.XMLDOM");
                outDoc.loadXML(content);
                return outDoc;
            }
            else
            {
                return postProcessJs(content);
            }
        }

        switch(output)
        {
            default:
            case 'string':
            {
                return postProcessJs(content);
            }
            case 'dom':
            {
                var f = document.createDocumentFragment();
                var d = pf.$$$('div');
                d.innerHTML = content;
                while (d.firstChild)
                {
                     f.appendChild(d.firstChild);
                }
                return f;
            }
        }
    };

    this.transform = function(xml, xsl, arg, opts)
    {
        switch(pf.$type(opts))
        {
            case 'function':
                opts = pf.hash.merge(this.defaultOpts, {'callback': opts});
                break;
            case 'string':
                opts = pf.hash.merge(this.defaultOpts, {'container': pf.$(opts)});
                break;
            case 'element':
                opts = pf.hash.merge(this.defaultOpts, {'container': opts});
                break;
            case 'object':
                opts = pf.hash.merge(this.defaultOpts, opts);
                break;
            default:
                throw pf.$exGen('xslt.transform(): typeof(options) argument is ' + pf.$type(opts) + ', expected function|string|element|hash');
        }

        var async = opts['async'];
        var Engine = null;

        if(typeof XSLTProcessor != "undefined")
        {
            Engine = doTransformNative;
        }
        else if(window.ActiveXObject)
        {
            Engine = doTransformIE;
        }
        else
        {
            throw pf.$exGen('xslt.transform(): no Engine found');
            return false;
        }

        try
        {
            var xmlObj = getXML(xml, 'xml', async ? _prepareXSL : null);
            var xslObj = null;

            if (!async || xmlObj) return(_prepareXSL(xmlObj));
        }
        catch(e)
        {
            debugError('Transform: '+e.description);
            return false;
        }

        function _prepareXSL(_xmlObj)
        {
            xmlObj = _xmlObj;
            xslObj = getXSL(xsl, async ? _doTransform : null);
            if (!async || xslObj) return(_doTransform(xslObj));
        }

        function _doTransform(_xslObj)
        {
            xslObj = _xslObj;
            if (!xmlObj || !xslObj) { debugError('Empty transf'); return false; }
            var output = Engine(xmlObj, xslObj, arg || {}, getOutputMethod(xslObj), opts['output']);
            if (!output) { output = ''; /* return false;*/ }
            if (opts['container']) { opts['container'].innerHTML = output; }
            return (async && pf.$type(opts['callback']) == 'function') ? opts['callback'](output) : output;
        }

    };

    this.transformXML = this.transform;

    function postProcessJs(input)
    {
        if ((typeof input != 'string') || (input.indexOf('jsFunction') == -1)) { return input; }

        function execJs(full, func, args)
        {
            return eval(func+'('+args+')')
        };
        return input.replace(/jsFunction\('([\w.]+)', (.*?')\)/g, execJs);
    };

}
window.XSLTTransformer = new CXSLTTransformer();

