<?php
/**
 * This class creates the actual XML data and passes it on to a ContentHandler
 * for optional WBXML encoding.
 *
 * Each member function creates one type of SyncML artefact (like a Status
 * response).  Currently some of the information is retrieved from
 * state. Maybe remove these dependencies (by providing the data as parameter)
 * for an even cleaner implementation.
 *
 * The SyncML_XMLOutput class takes automatically care of creating a unique
 * CmdID for each command created.
 *
 * $Horde: framework/SyncML/SyncML/XMLOutput.php,v 1.14.2.3 2008/04/08 23:53:08 jan Exp $
 *
 * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Karsten Fourmont <karsten@horde.org>
 * @since   Horde 3.2
 * @package SyncML
 */

class SyncML_XMLOutput {

    /**
     * The CmdID provides a unique ID for each command in a syncml packet.
     */
    var $_msg_CmdID;

    /**
     *  The outputhandler to whom the XML is passed: like  XML_WBXML_Encoder
     */
    var $_output;

    var $_uri;

    /**
     * The final output as procuded by the _output Encoder. Either an
     * XML string or a WBXML string.
     */
    function getOutput()
    {
        return $this->_output->getOutput();
    }

    /**
     * The length of the output as produced by the Encoder. To limit the
     * size of individual messages.
     */
    function getOutputSize()
    {
        return $this->_output->getOutputSize();
    }

    /**
     * To we create wbxml or not?
     */
    function isWBXML()
    {
        return is_a($this->_output, 'XML_WBXML_Encoder');
    }

    function &singleton()
    {
        static $instance;
        if (!isset($instance)) {
            $instance = new SyncML_XMLOutput();
        }
        return $instance;
    }

    function init(&$theoutputhandler)
    {
        $this->_output = $theoutputhandler;
        $this->_msg_CmdID = 1;

    }

    /** creates a SyncHdr output.
     * All required data is retrieved from state.
     */
    function outputHeader()
    {
        $state = &$_SESSION['SyncML.state'];
        $attrs = array();
        $this->_uriMeta = $state->uriMeta;

        $this->_output->startElement($this->_uri, 'SyncHdr', $attrs);

        $this->_output->startElement($this->_uri, 'VerDTD', $attrs);
        $chars = $state->getVerDTD();
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'VerDTD');

        $this->_output->startElement($this->_uri, 'VerProto', $attrs);
        $chars = $state->getProtocolName();
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'VerProto');

        $this->_output->startElement($this->_uri, 'SessionID', $attrs);
        $this->_output->characters($state->sessionID);
        $this->_output->endElement($this->_uri, 'SessionID');

        $this->_output->startElement($this->_uri, 'MsgID', $attrs);
        $this->_output->characters($state->messageID);
        $this->_output->endElement($this->_uri, 'MsgID');

        $this->_output->startElement($this->_uri, 'Target', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        // Source URI sent from client is Target for the server
        $this->_output->characters($state->sourceURI);
        $this->_output->endElement($this->_uri, 'LocURI');
        if ($state->user) {
            $this->_output->startElement($this->_uri, 'LocName', $attrs);
            $this->_output->characters($state->user);
            $this->_output->endElement($this->_uri, 'LocName');
        }
        $this->_output->endElement($this->_uri, 'Target');

        $this->_output->startElement($this->_uri, 'Source', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        // Target URI sent from client is Source for the server
        $this->_output->characters($state->targetURI);
        $this->_output->endElement($this->_uri, 'LocURI');
        $this->_output->endElement($this->_uri, 'Source');

        /*
        Do not send RespURI Element. It's optional and may be misleading:
        $this->_targetURI is data from the client and not guaranteed to be
        the correct URL for access. Let the client just stay with the same
        URL it has used for the previous request(s).
        */
        //$this->_output->startElement($this->_uri, 'RespURI', $attrs);
        //$this->_output->characters($state->targetURI);
        //$this->_output->endElement($this->_uri, 'RespURI');

        // @Todo: omit this in SyncML1.0?
        $this->_output->startElement($this->_uri, 'Meta', $attrs);

        // Dummy Max MsqSize, this is just put in to make the packet
        // work, it is not a real value.
        $this->_output->startElement($this->_uriMeta, 'MaxMsgSize', $attrs);
        $chars = SERVER_MAXMSGSIZE; // 1Meg
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uriMeta, 'MaxMsgSize');


        // MaxObjSize, required by protocol for SyncML1.1 and higher.
        if ($state->version > 0) {
            $this->_output->startElement($this->_uriMeta, 'MaxObjSize', $attrs);
            $this->_output->characters(SERVER_MAXOBJSIZE);
            $this->_output->endElement($this->_uriMeta, 'MaxObjSize');
        }
        $this->_output->endElement($this->_uri, 'Meta');

        $this->_output->endElement($this->_uri, 'SyncHdr');
    }

    function outputInit()
    {
        $this->_uri = $_SESSION['SyncML.state']->getURI();

        $this->_output->startElement($this->_uri, 'SyncML', array());
    }

    function outputBodyStart()
    {
        $this->_output->startElement($this->_uri, 'SyncBody', array());
    }

    function outputFinal()
    {
        $this->_output->startElement($this->_uri, 'Final', array());
        $this->_output->endElement($this->_uri, 'Final');
    }

    function outputEnd()
    {
        $this->_output->endElement($this->_uri, 'SyncBody', array());
        $this->_output->endElement($this->_uri, 'SyncML', array());
    }


    function outputStatus($cmdRef, $cmd, $data,
                         $targetRef = '', $sourceRef = '',
                         $syncAnchorNext = '',
                         $syncAnchorLast = '')
    {
        $state = &$_SESSION['SyncML.state'];
        $uriMeta = $state->uriMeta;
        $attrs = array();

        $this->_output->startElement($this->_uri, 'Status', $attrs);
        $this->_outputCmdID();

        $this->_output->startElement($this->_uri, 'MsgRef', $attrs);
        $chars = $state->messageID;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'MsgRef');

        $this->_output->startElement($this->_uri, 'CmdRef', $attrs);
        $chars = $cmdRef;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'CmdRef');

        $this->_output->startElement($this->_uri, 'Cmd', $attrs);
        $chars = $cmd;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'Cmd');

        if (!empty($targetRef)) {
            $this->_output->startElement($this->_uri, 'TargetRef', $attrs);
            $this->_output->characters($targetRef);
            $this->_output->endElement($this->_uri, 'TargetRef');
        }

        if (!empty($sourceRef)) {
            $this->_output->startElement($this->_uri, 'SourceRef', $attrs);
            $this->_output->characters($sourceRef);
            $this->_output->endElement($this->_uri, 'SourceRef');
        }

        // If we are responding to the SyncHdr and we are not
        // authenticated then request basic authorization.
        if ($cmd == 'SyncHdr' && !$state->authenticated) {
            // keep RESPONSE_CREDENTIALS_MISSING, otherwise set to RESPONSE_INVALID_CREDENTIALS
            $data = ($data== RESPONSE_CREDENTIALS_MISSING)
                ? RESPONSE_CREDENTIALS_MISSING
                : RESPONSE_INVALID_CREDENTIALS;

            $this->_output->startElement($this->_uri, 'Chal', $attrs);
            $this->_output->startElement($this->_uri, 'Meta', $attrs);

            $this->_output->startElement($uriMeta, 'Type', $attrs);
            $this->_output->characters('syncml:auth-basic');
            $this->_output->endElement($uriMeta, 'Type');

            $this->_output->startElement($uriMeta, 'Format', $attrs);
            $this->_output->characters('b64');
            $this->_output->endElement($uriMeta, 'Format');

            $this->_output->endElement($this->_uri, 'Meta');
            $this->_output->endElement($this->_uri, 'Chal');

        }

        $this->_output->startElement($this->_uri, 'Data', $attrs);
        $this->_output->characters($data);
        $this->_output->endElement($this->_uri, 'Data');

        if (!empty($syncAnchorNext) || !empty($syncAnchorNLast)) {
            $this->_output->startElement($this->_uri, 'Item', $attrs);
            $this->_output->startElement($this->_uri, 'Data', $attrs);

            $this->_output->startElement($uriMeta, 'Anchor', $attrs);

            if (!empty($syncAnchorLast)) {
              $this->_output->startElement($uriMeta, 'Last', $attrs);
              $this->_output->characters($syncAnchorLast);
              $this->_output->endElement($uriMeta, 'Last');
            }

            if (!empty($syncAnchorNext)) {
              $this->_output->startElement($uriMeta, 'Next', $attrs);
              $this->_output->characters($syncAnchorNext);
              $this->_output->endElement($uriMeta, 'Next');
            }

            $this->_output->endElement($uriMeta, 'Anchor');

            $this->_output->endElement($this->_uri, 'Data');
            $this->_output->endElement($this->_uri, 'Item');
        }

        $this->_output->endElement($this->_uri, 'Status');

    }

    function outputDevInf($cmdRef)
    {
        $state = &$_SESSION['SyncML.state'];
        $uriMeta = $state->uriMeta;
        $uriDevInf = $state->uriDevInf;
        $attrs = array();

        $this->_output->startElement($this->_uri, 'Results', $attrs);
        $this->_outputCmdID();

        $this->_output->startElement($this->_uri, 'MsgRef', $attrs);
        $chars = $state->messageID;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'MsgRef');

        $this->_output->startElement($this->_uri, 'CmdRef', $attrs);
        $chars = $cmdRef;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'CmdRef');

        $this->_output->startElement($this->_uri, 'Meta', $attrs);
        $this->_output->startElement($uriMeta, 'Type', $attrs);
        if ($state->wbxml) {
            $this->_output->characters(MIME_SYNCML_DEVICE_INFO_WBXML);
        } else {
            $this->_output->characters(MIME_SYNCML_DEVICE_INFO_XML);
        }

        $this->_output->endElement($uriMeta, 'Type');
        $this->_output->endElement($this->_uri, 'Meta');

        $this->_output->startElement($this->_uri, 'Item', $attrs);
        $this->_output->startElement($this->_uri, 'Source', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        $this->_output->characters($state->getDevInfURI());
        $this->_output->endElement($this->_uri, 'LocURI');
        $this->_output->endElement($this->_uri, 'Source');

        $this->_output->startElement($this->_uri, 'Data', $attrs);

        /* DevInf data is stored in wbxml not as a seperate codepage but
         * rather as a complete wbxml stream as opaque data.  So we need a
         * new Handler. */
        $devinfoutput = $this->_output->createSubHandler();

        $devinfoutput->startElement($uriDevInf , 'DevInf', $attrs);
        $devinfoutput->startElement($uriDevInf , 'VerDTD', $attrs);
        $devinfoutput->characters($state->getVerDTD());
        $devinfoutput->endElement($uriDevInf , 'VerDTD', $attrs);
        $devinfoutput->startElement($uriDevInf , 'Man', $attrs);
        $devinfoutput->characters('The Horde Project (http://www.horde.org)');
        $devinfoutput->endElement($uriDevInf , 'Man', $attrs);
        $devinfoutput->startElement($uriDevInf , 'DevID', $attrs);
        $devinfoutput->characters(isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : 'localhost');
        $devinfoutput->endElement($uriDevInf , 'DevID', $attrs);
        $devinfoutput->startElement($uriDevInf , 'DevTyp', $attrs);
        $devinfoutput->characters('server');
        $devinfoutput->endElement($uriDevInf , 'DevTyp', $attrs);

        if ($state->version > 0) {
            $devinfoutput->startElement($uriDevInf , 'SupportLargeObjs', $attrs);
            $devinfoutput->endElement($uriDevInf , 'SupportLargeObjs', $attrs);

            $devinfoutput->startElement($uriDevInf , 'SupportNumberOfChanges', $attrs);
            $devinfoutput->endElement($uriDevInf , 'SupportNumberOfChanges', $attrs);
        }
        $this->_writeDataStore('notes', 'text/x-vnote', '1.1', $devinfoutput,
                               array('text/plain' => '1.0'));
        $this->_writeDataStore('contacts', 'text/x-vcard', '3.0', $devinfoutput,
                               array('text/x-vcard' => '2.1'));
        $this->_writeDataStore('tasks', 'text/calendar', '2.0', $devinfoutput,
                               array('text/x-vcalendar' => '1.0'));
        $this->_writeDataStore('calendar', 'text/calendar', '2.0', $devinfoutput,
                               array('text/x-vcalendar' => '1.0'));
        $devinfoutput->endElement($uriDevInf , 'DevInf', $attrs);

        $this->_output->opaque($devinfoutput->getOutput());
        $this->_output->endElement($this->_uri, 'Data');
        $this->_output->endElement($this->_uri, 'Item');
        $this->_output->endElement($this->_uri, 'Results');
    }

    /**
     * Writes DevInf data for one DataStore.
     *
     * @param string $sourceref: data for SourceRef element.
     * @param string $mimetype: data for &lt;(R|T)x-Pref&gt;&lt;CTType&gt;
     * @param string $version: data for &lt;(R|T)x-Pref&gt;&lt;VerCT&gt;
     * @param string &$output contenthandler that will received the output.
     * @param array $additionaltypes: array of additional types for Tx and Rx;
     *              format array('text/vcard' => '2.0')
     */
    function _writeDataStore($sourceref, $mimetype, $version,&$output,
                             $additionaltypes = false)
    {
        $uriDevInf = $_SESSION['SyncML.state']->uriDevInf;
        $attrs = array();

        $output->startElement($uriDevInf , 'DataStore', $attrs);
        $output->startElement($uriDevInf , 'SourceRef', $attrs);
        $output->characters($sourceref);
        $output->endElement($uriDevInf , 'SourceRef', $attrs);

        $output->startElement($uriDevInf , 'Rx-Pref', $attrs);
        $output->startElement($uriDevInf , 'CTType', $attrs);
        $output->characters($mimetype);
        $output->endElement($uriDevInf , 'CTType', $attrs);
        $output->startElement($uriDevInf , 'VerCT', $attrs);
        $output->characters($version);
        $output->endElement($uriDevInf , 'VerCT', $attrs);
        $output->endElement($uriDevInf , 'Rx-Pref', $attrs);

        if (is_array($additionaltypes)) {
            foreach ($additionaltypes as $ct => $ctver){
                $output->startElement($uriDevInf , 'Rx', $attrs);
                $output->startElement($uriDevInf , 'CTType', $attrs);
                $output->characters($ct);
                $output->endElement($uriDevInf , 'CTType', $attrs);
                $output->startElement($uriDevInf , 'VerCT', $attrs);
                $output->characters($ctver);
                $output->endElement($uriDevInf , 'VerCT', $attrs);
                $output->endElement($uriDevInf , 'Rx', $attrs);
            }
        }

        $output->startElement($uriDevInf , 'Tx-Pref', $attrs);
        $output->startElement($uriDevInf , 'CTType', $attrs);
        $output->characters($mimetype);
        $output->endElement($uriDevInf , 'CTType', $attrs);
        $output->startElement($uriDevInf , 'VerCT', $attrs);
        $output->characters($version);
        $output->endElement($uriDevInf , 'VerCT', $attrs);
        $output->endElement($uriDevInf , 'Tx-Pref', $attrs);

        if (is_array($additionaltypes)) {
            foreach ($additionaltypes as $ct => $ctver){
                $output->startElement($uriDevInf , 'Tx', $attrs);
                $output->startElement($uriDevInf , 'CTType', $attrs);
                $output->characters($ct);
                $output->endElement($uriDevInf , 'CTType', $attrs);
                $output->startElement($uriDevInf , 'VerCT', $attrs);
                $output->characters($ctver);
                $output->endElement($uriDevInf , 'VerCT', $attrs);
                $output->endElement($uriDevInf , 'Tx', $attrs);
            }
        }

        $output->startElement($uriDevInf , 'SyncCap', $attrs);
        // We support all sync Types from 1-6:
        // two way, slow, refresh|update from client|server
        for ($i=1; $i<=6; ++$i) {
            $output->startElement($uriDevInf , 'SyncType', $attrs);
            $output->characters($i);
            $output->endElement($uriDevInf , 'SyncType', $attrs);
        }
        $output->endElement($uriDevInf , 'SyncCap', $attrs);
        $output->endElement($uriDevInf , 'DataStore', $attrs);
    }

    function outputAlert($alertCode, $clientDB = '', $serverDB = '', $lastAnchor = '', $nextAnchor = '')
    {
        $uriMeta = $_SESSION['SyncML.state']->uriMeta;
        $attrs = array();

        $this->_output->startElement($this->_uri, 'Alert', $attrs);
        $this->_outputCmdID();

        $this->_output->startElement($this->_uri, 'Data', $attrs);
        $chars = $alertCode;
        $this->_output->characters($chars);
        $this->_output->endElement($this->_uri, 'Data');

        $this->_output->startElement($this->_uri, 'Item', $attrs);

        if (!empty($clientDB)) {
            $this->_output->startElement($this->_uri, 'Target', $attrs);
            $this->_output->startElement($this->_uri, 'LocURI', $attrs);
            $this->_output->characters($clientDB);
            $this->_output->endElement($this->_uri, 'LocURI');
            $this->_output->endElement($this->_uri, 'Target');
        }

        if (!empty($serverDB)) {
            $this->_output->startElement($this->_uri, 'Source', $attrs);
            $this->_output->startElement($this->_uri, 'LocURI', $attrs);
            $this->_output->characters($serverDB);
            $this->_output->endElement($this->_uri, 'LocURI');
            $this->_output->endElement($this->_uri, 'Source');
        }

        $this->_output->startElement($this->_uri, 'Meta', $attrs);

        $this->_output->startElement($uriMeta, 'Anchor', $attrs);

        $this->_output->startElement($uriMeta, 'Last', $attrs);
        $this->_output->characters($lastAnchor);
        $this->_output->endElement($uriMeta, 'Last');

        $this->_output->startElement($uriMeta, 'Next', $attrs);
        $this->_output->characters($nextAnchor);
        $this->_output->endElement($uriMeta, 'Next');

        $this->_output->endElement($uriMeta, 'Anchor');


        // MaxObjSize, required by protocol for SyncML1.1 and higher.
        if ($_SESSION['SyncML.state']->version > 0) {
            $this->_output->startElement($uriMeta, 'MaxObjSize', $attrs);
            $this->_output->characters(SERVER_MAXOBJSIZE);
            $this->_output->endElement($uriMeta, 'MaxObjSize');
        }
        $this->_output->endElement($this->_uri, 'Meta');

                $this->_output->endElement($this->_uri, 'Item');
        $this->_output->endElement($this->_uri, 'Alert');

    }


    function outputGetDevInf()
    {
        $state = &$_SESSION['SyncML.state'];
        $uriMeta = $state->uriMeta;
        $attrs = array();

        $this->_output->startElement($this->_uri, 'Get', $attrs);
        $this->_outputCmdID();

        $this->_output->startElement($this->_uri, 'Meta', $attrs);
        $this->_output->startElement($uriMeta, 'Type', $attrs);
        $attrs = array();
        if ($state->wbxml) {
            $chars = MIME_SYNCML_DEVICE_INFO_WBXML;
        } else {
            $chars = MIME_SYNCML_DEVICE_INFO_XML;
        }
        $this->_output->characters($chars);
        $this->_output->endElement($uriMeta, 'Type');
        $this->_output->endElement($this->_uri, 'Meta');

        $this->_output->startElement($this->_uri, 'Item', $attrs);
        $this->_output->startElement($this->_uri, 'Target', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        $this->_output->characters($state->getDevInfURI());
        $this->_output->endElement($this->_uri, 'LocURI');
        $this->_output->endElement($this->_uri, 'Target');
        $this->_output->endElement($this->_uri, 'Item');

        $this->_output->endElement($this->_uri, 'Get');
    }

    /**
     * Output a single Sync command (Add, Delete, Replace).
     */
    function outputSyncCommand($command,
                           $content=null, $contentType = null,
                           $encodingType = null,
                           $cuid = null, $suid = null)
    {
        $uriMeta = $_SESSION['SyncML.state']->uriMeta;
        $attrs = array();

        $this->_output->startElement($this->_uri, $command, $attrs);
        $this->_outputCmdID();

        if (isset($contentType)) {
            $this->_output->startElement($this->_uri, 'Meta', $attrs);
            $this->_output->startElement($uriMeta, 'Type', $attrs);
            $this->_output->characters($contentType);
            $this->_output->endElement($uriMeta, 'Type');
            $this->_output->endElement($this->_uri, 'Meta');
        }

        if (isset($content)
            || isset($cuid) || isset($suid)) {
            $this->_output->startElement($this->_uri, 'Item', $attrs);
            if ($suid != null) {
                $this->_output->startElement($this->_uri, 'Source', $attrs);
                $this->_output->startElement($this->_uri, 'LocURI', $attrs);
                $this->_output->characters($suid);
                $this->_output->endElement($this->_uri, 'LocURI');
                $this->_output->endElement($this->_uri, 'Source');
            }

            if ($cuid != null) {
                $this->_output->startElement($this->_uri, 'Target', $attrs);
                $this->_output->startElement($this->_uri, 'LocURI', $attrs);
                $this->_output->characters($cuid);
                $this->_output->endElement($this->_uri, 'LocURI');
                $this->_output->endElement($this->_uri, 'Target');
            }

            if (!empty($encodingType)) {
                $this->_output->startElement($this->_uri, 'Meta', $attrs);
                $this->_output->startElement($uriMeta, 'Format', $attrs);
                $this->_output->characters($encodingType);
                $this->_output->endElement($uriMeta, 'Format');
                $this->_output->endElement($this->_uri, 'Meta');
            }
            if (isset($content)) {
                $this->_output->startElement($this->_uri, 'Data', $attrs);
                if($this->isWBXML()) {
                    $this->_output->characters($content);
                } else {
                    $device = $_SESSION['SyncML.state']->getDevice();
                    if ($device->useCdataTag()) {
                        /* Enclose data in CDATA if possible to avoid */
                        /* problems with &,< and >. */
                        $this->_output->characters('<![CDATA[' . $content . ']]>');
                    } else {
                        $this->_output->characters($content);
                    }
                }
                $this->_output->endElement($this->_uri, 'Data');
            }
            $this->_output->endElement($this->_uri, 'Item');
        }

        $this->_output->endElement($this->_uri, $command);
    }

    function outputSyncStart($clientLocURI,$serverLocURI, $numberOfChanges = null)
    {
        $attrs = array();

        $this->_output->startElement($this->_uri, 'Sync', $attrs);
        $this->_outputCmdID();

        $this->_output->startElement($this->_uri, 'Target', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        $this->_output->characters($clientLocURI);
        $this->_output->endElement($this->_uri, 'LocURI');
        $this->_output->endElement($this->_uri, 'Target');

        $this->_output->startElement($this->_uri, 'Source', $attrs);
        $this->_output->startElement($this->_uri, 'LocURI', $attrs);
        $this->_output->characters($serverLocURI);
        $this->_output->endElement($this->_uri, 'LocURI');
        $this->_output->endElement($this->_uri, 'Source');

        if (is_int($numberOfChanges)) {
            $this->_output->startElement($this->_uri, 'NumberOfChanges', $attrs);
            $this->_output->characters($numberOfChanges);
            $this->_output->endElement($this->_uri, 'NumberOfChanges');
        }

    }

    function outputSyncEnd()
    {
        $this->_output->endElement($this->_uri, 'Sync');
    }


    //  internal helper functions:

    function _outputCmdID() {
        $attrs = array();

        $this->_output->startElement($this->_uri, 'CmdID', $attrs);
        $this->_output->characters($this->_msg_CmdID);
        $this->_msg_CmdID += 1;
        $this->_output->endElement($this->_uri, 'CmdID');

    }

    /**
     * Output a single <ele>$str</ele> element.
     */
    function _singleEle($tag, $str, $uri = null)
    {
        if (empty($uri)) {
            $uri = $this->_uri;
        }
        $attrs = array();
        $this->_output->startElement($uri, $tag, $attrs);
        $this->_output->characters($str);
        $this->_output->endElement($uri, $tag);
    }

}
