<?php

/**
 * Project: 
 * Contenido Content Management System
 * 
 * Description: 
 * XSLT_Processor class
 * 
 * Requirements: 
 * @con_php_req 5.0
 * 
 *
 * @package    Contenido_XML
 * @version    1.0.0
 * @author     Jan Lengowski
 * @copyright  four for business AG <www.4fb.de>
 * @license    http://www.contenido.org/license/LIZENZ.txt
 * @link       http://www.4fb.de
 * @link       http://www.contenido.org
 * @since      file available since contenido release <= 4.6
 * 
 * {@internal 
 *   created unknown
 *   modified 2008-06-30, Dominik Ziegler, add security fix
 *
 *   $Id$:
 * }}
 * 
 */
if (!defined('CON_FRAMEWORK')) {
    die('Illegal call');
}

/**
 * XSLT_Processor 
 * 
 * Wrapper class for the Sablotron XSLT extension
 *
 * !!! _REQUIRES_ Installed Sablotron to run !!!
 *
 * Example:
 * 
 * $xslt = new XSLT_Processor;
 * 
 * $xslt->setXmlFile("foo.xml");
 * $xslt->setXslFile("bar.xslt");
 *  
 * $html = $xslt->process();
 *
 */
class XsltProcessor {

    /**
     * XSML Processor auto-free
     * @var bool
     * @access private
     */
    var $autofree = true;

    /**
     * Error message string
     * @var string
     * @access private
     */
    var $error = "";

    /**
     * Error number
     * @var int
     * @access private
     */
    var $errno = 0;

    /**
     * The result of the XSLT Transformation
     * @var string
     * @access private
     */
    var $result = "";

    /**
     * The XML String for the Transformation
     * @var string
     * @access private
     */
    var $xml = "";

    /**
     * The XSLT String for the Transformation
     * @var string
     * @access private
     */
    var $xslt = "";

    /**
     * XSLT Processor
     * @var object
     * @access private
     */
    var $processor;

    /**
     * XSLT Process arguments array
     * @var array
     * @access private
     */
    var $arguments = array();

    /**
     * XSLT Process parameters array
     * @var array
     * @access private
     */
    var $parameters = array();

    /**
     * Constructor
     * @access private
     */
    function __construct() {
        $this->_init();
    }

    /**
     * Initialize the class
     * @access private
     * @return void
     */
    function _init() {
        if (!function_exists("xslt_create")) {
            die("Cannot instantiate XSLT Class \n XSLT not supported");
        }

        $this->processor = xslt_create();
    }

    /**
     * Translate literal to numeric entities to avoid
     * the 'undefined entity error' that a literal
     * entity would cause.
     * 
     * @param string XML String with literal entities
     * @return string XML string with numeric entites
     * @access private
     */
    function literal2NumericEntities($stringXml) {

        $literal2NumericEntity = array();

        if (empty($literal2NumericEntity)) {
            $transTbl = clGetHtmlTranslationTable(HTML_ENTITIES);

            foreach ($transTbl as $char => $entity) {
                if (strpos('&"<>', $char) !== FALSE)
                    continue;
                $literal2NumericEntity[$entity] = '&#' . ord($char) . ';';
            }
        }

        return strtr($stringXml, $literal2NumericEntity);
    }

    /**
     * Set the XML to be Transformed
     * @param string The XML String
     * @return void
     * @access public
     */
    function setXml($xml) {
        $this->arguments["/_xml"] = $this->literal2NumericEntities($xml);
    }

    /**
     * Set the XSLT for the Transformation
     * @param string The XML String
     * @return void
     * @access public
     */
    function setXsl($xsl) {
        $this->arguments["/_xsl"] = $this->literal2NumericEntities($xsl);
    }

    /**
     * Set the XML-File to be Transformed
     * @param string Location of the XML file
     * @return void
     * @access public
     */
    function setXmlFile($file) {
        $xml = $this->readFromFile($file);
        $this->arguments["/_xml"] = $this->literal2NumericEntities($xml);
    }

    /**
     * Set the XSL-File for the Transformation
     * @param string Location of the XSL file
     * @return void
     * @access public
     */
    function setXslFile($file) {
        $xsl = $this->readFromFile($file);
        $this->arguments["/_xsl"] = $this->literal2NumericEntities($xsl);
    }

    /**
     * Return the contents of a file if
     * the passed parameter is a file.
     *
     * @param string File location
     * @return string File contents	
     * @access private
     */
    function readFromFile($file) {
        if (file_exists($file)) {
            $data = file($file);
            $data = join($data, "");
            return $data;
        }

        die("<span style=\"color: red\"><b>ERROR:</b></span> File not found: <b>$file</b>");
    }

    /**
     * Pass top level parameters to the XSLT processor.
     * The parameters can be accessed in XSL 
     * with <xsl:param name="paramname"/>
     * 
     * @param string Name
     * @param string Value
     * @return void
     */
    function setParam($name, $value) {
        $this->parameters[$name] = utf8_encode($value);
    }

    /**
     * Define external scheme handlers for the XSLT Processor.
     *  
     * Example param array: 
     * 	
     * array("get_all", "mySchemeHandler")
     * 
     * Example scheme handler function:
     * 
     * function mySchemeHandler($processor, $scheme, $param) 
     * {
     *     // to remove the first slash added by Sablotron
     * 	   $param = substr($param, 1);    
     *
     * 	   if ($scheme == 'file_exists')
     * 	   {   // result is returned as valid xml string 
     * 	       return '<?xml version="1.0" encoding="UTF-8"?><root>'.(file_exists($param) ? "true" : "false")."</root>";
     *     }   	    
     * }
     *  
     * To use the schema handler use:
     * <xsl:if test="document('file_exists:somefile.xml�)/root = 'true'">
     *     do something 
     * </xsl:if>
     * 	
     * To call the external function use the 'document()' XSLT-Function.
     *
     * <xsl:value-of select="document('schemename:parameter')/root"/>
     *  
     * Schemename and parameter will be passed to the handler function as second and third parameter.
     * The return value of the function must be valid XML to access it using XPath.
     *
     * @param array array("scheme"=>"schemeHandlerName");
     * @return void
     * @access public
     */
    function setSchemeHandlers($aHandlers) {
        xslt_set_scheme_handlers($this->processor, $aHandlers);
    }

    /**
     * Transform the XML data using the XSL and
     * return the results of the transformation 
     *
     * @return string Transformed data
     * @access public
     */
    function process() {
        $this->result = xslt_process($this->processor, "arg:/_xml", "arg:/_xsl", NULL, $this->arguments, $this->parameters);
        $this->error = xslt_error($this->processor);
        $this->errno = xslt_errno($this->processor);

        if ($this->autofree) {
            xslt_free($this->processor);
        }

        return $this->result;
    }

    /**
     * Prints the Error message and number
     * if an error occured
     *
     * @return void
     * @access public
     */
    function printErrors() {
        if ($this->errno > 0) {
            echo "<b>Error Number: </b><span style=\"color:red\">" . $this->errno . "</span> ";
            echo "<b>Error Message: </b><span style=\"color:red\">" . $this->error . "</span>";
        }
    }

    /**
     * Manual free of the parser
     * @return void
     */
    function free() {
        xslt_free($this->processor);
    }

}
