<?php
// Modification log
// (date)       (author)    (activity/purpose)
// 22.08.03     T.Gildseth  Added Assertion checking for parameters
// 22.08.03     T.Gildseth  Added and updated comments to comply with phpdoc(phpdoc.de)
// 22.10.03     T.Gildseth  Renamed _checkAssert to _checkID
// 22.10.03     T.Gildseth  Moved printing of stacktrace to _printStackTrace
// 22.10.03     T.Gildseth  Added function _checkAssert
// 22.10.03     T.Gildseth  Added assertion checking in _findIDElements and getDoc
//
// Todo
// (date)       (author)    (activity/purpose)
//

require_once 'classes/XML/class.XmlParser.php';
assert_options(ASSERT_WARNING0);

/**
 * This class creates a XML tree from a XHTML document
 *
 * This class creates a XML tree from a XHTML document(Strictly speaking, an XML document.
 * This class does not verify that the document validates as XHTML),
 * and lets the user easily access and modify the attribute and values of elements having
 * and id attribute.<br>
 * Example usage:<br>
 * <code>
 * $doc = new XHTMLDoc('&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;title&amp;gt;test&amp;lt;/title&amp;gt;
                        &amp;lt;/head&amp;gt;&amp;lt;body id="test"&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;');<br>
 * $doc->setValue('test', 'Document body');<br>
 * echo $doc->getDoc();<br>
 * </code>
 * <br>
 * The result will be:<br>
 * <code>
 * &amp;lt;html&amp;gt;<br>
 *  &amp;lt;head&amp;gt;<br>
 *   &amp;lt;title&amp;gt;test&amp;lt;/title&amp;gt;<br>
 *  &amp;lt;/head&amp;gt;<br>
 * &amp;lt;body id="test"&amp;gt;<br>
 * Document body<br>
 * &amp;lt;/body&amp;gt;<br>
 * &amp;lt;/html&amp;gt;<br>
 * </code>
 *
 * @author Tommy Gildseth <tommy@akili.no>;
 * @author Akili AS <post@akili.no>;
 * @copyright Akili AS, June 2003
 * @access public
 * @package XHTML
 */
class XHTMLDoc
    
{
    
//{{{ Class members
    /**
     * A reference to the root XmlNode of the XML tree representing the XHTML document
     * @access private
     */
    
var $_xmlTree;

    
/**
     * This is the declaration which appears at the top of the XHTML document
     * @access private
     */
    
var $_docType;

    
/**
     * Elements in the document, which have an id attribute.
     * @access private
     */
    
var $_elements;

    
/**
     * The XML parser.
     * @access private
     */
    
var $_parser;
    
//}}} End class member declarations


    //{{{Constructor
    /**
     * XHTMLDoc constructor
     *
     * @param string $document Either a string containing the XHTML document, or the
     *                         filename which contains it.
     * @access public
     */
    
function XHTMLDoc$document )
        {
        
$this->_parser = new XmlParser();

        if (
strlen($document) < 100 && is_file($document))
            
$this->_parser->parseFile($document);
        else
            
$this->_parser->parseString($document);

        
$this->_xmlTree $this->_parser->getRootNode();
        if (
is_object($this->_xmlTree))
            {
            
$this->_findIDElements($this->_xmlTree);
            
$this->_docType $this->_parser->doctype;
            
$this->_parser->reset();
            }
        }
//}}}end Constructor

    //{{{_findIDElements
    /**
     * Find all elements which have an id attribute
     *
     * @param object XmlNode &$node reference to a XMLNode
     * @access private
     */
    
function _findIDElements( &$node )
        {
        if (!
$this->_checkAssert('_findIDElements',
                                 
'is_object($param)',
                                 
'INTERNAL ERROR:<br/>parameter $node is not an XmlNode',
                                 
$node))
            {
            return 
false;
            }

        
$children = &$node->getChildren(NUMERIC);
        
$id $node->getAttribute('id');
        if (
$id !== null)
            {
            
$this->_elements[$id] = &$node;
            }

        
$j count($children);
        for (
$i 0$i $j; ++$i)
            {
            
$this->_findIDElements$children[$i] );
            }
        }
//}}}end function _findFormElements

    //{{{setValue
    /**
     * Set the value of the element identified by $id
     *
     * @param string $id the value of the id attribute of a XHTML element
     *                   (note: lowercase, ie. not ID)
     * @param string $value The value to assign to the element.
     * @access public
     */
    
function setValue$id$value )
        {
        if (!
$this->_checkID('setValue'$id))
            return;

        
$this->_elements[$id]->setValue($value);
        }
//}}}end function setValue

    //{{{appendValue
    /**
     * Adds the value to the end of the element identified by $id's content
     *
     * @param string $id the value of the id attribute of a XHTML element (note: lowercase, ie. not ID)
     * @param string $value The value to assign to the element.
     * @access public
     */
    
function appendValue$id$value )
        {
        if (!
$this->_checkID('appendValue'$id))
            return;

        
$this->_elements[$id]->appendValue($value);
        }
//}}}end function appendValue

    //{{{addAttribute
    /**
     * Add a attribute to the element identified by id
     *
     * @param string $id the value of the id attribute of a XHTML element
     *                   (note: lowercase, ie. not ID)
     * @param string $name Name of the attribute to add to the element
     * @param string $value The value to assign to the attribute.
     * @access public
     */
    
function addAttribute$id$name$value )
        {
        if (!
$this->_checkID('addAttribute'$id))
            return;

        
$this->_elements[$id]->addAttribute($name$value);
        }
//}}}end function addAttribute

    //{{{addAttributes
    /**
     * Similar to XHTMLDoc::addAttribute() except this one lets you add multiple attributes at once
     *
     * @param string $id the value of the id attribute of a XHTML element
     *                   (note: lowercase, ie. not ID)
     * @param string $attributes an associative array of attribute name =>
     *                           attribute value pairs
     * @access public
     */
    
function addAttributes$id$attributes )
        {
        if (!
$this->_checkID('addAttributes'$id))
            return;

        foreach(
$attributes as $attrName => $attrValue)
            {
            
$this->_elements[$id]->addAttribute($attrName$attrValue);
            }
        }
//}}}end function addAttributes

    //{{{addNode
    /**
     * Add a XmlNode to the children of the node identified by $id
     *
     * @param string $id the value of the id attribute of a XHTML element
     *                   (note: lowercase, ie. not ID)
     * @param object XmlNode $node The node to add
     * @access public
     */
    
function addNode$id$node )
        {
        if (!
$this->_checkID('addNode'$id))
            return;

        
$this->_elements[$id]->addChild('.'$node);
        
$this->_findIDElements($node);
        }
//}}}end function addNode

    //{{{remIDAttrib
    /**
     * Remove the ID attribute from all nodes
     *
     * @access public
     */
    
function remIDAttrib( )
        {
        foreach (
$this->_elements as $key=>$value)
            {
            
$this->_elements[$key]->remAttribute('id');
            }
        }
//}}}end function remIDAttrib

    //{{{insertXHTMLDoc
    /**
     * Add a XHTML docuemnt to the xmltree, as a child of the node identified by $id
     *
     * @param string $id the value of the id attribute of a XHTML element
     *                   (note: lowercase, ie. not ID)
     * @param string $doc Either a string containing the document, or a filename of a
     *                    file containing the document.
     * @access public
     */
    
function insertXHTML$id$doc)
        {
        if (!
$this->_checkID('addNode'$id))
            return;

        if (
strlen($doc) < 100 && is_file($doc))
            
$this->_parser->parseFile($doc);
        else
            
$this->_parser->parseString($doc);

        
$root $this->_parser->getRootNode();
        
$this->addNode($id$root);
        
$this->_findIDElements($this->_xmlTree);
        
$this->_parser->reset();
        }
//}}}end function insertXHTMLDoc

    //{{{getRootNode
    /**
     * Get the root node of the XHTML document
     *
     * @return object XmlNode The root node of the XML tree representing the XHTML document
     * @access public
     */
    
function getRootNode( )
        {
        return 
$this->_xmlTree;
        }
//}}}end function getRootNode

    //{{{getDoc
    /**
     * Get the XHTML document as a string
     *
     * @return string string representation of this XHTML document
     * @access public
     */
    
function getDoc( )
        {
        if (!
$this->_checkAssert('getDoc',
                                 
'!is_null($this->_xmlTree)',
                                 
'XHTML document is empty'))
            {
            return;
            }
        return 
$this->_docType."\n".$this->_xmlTree->getXmlString();
        }
//}}}end function getDoc

    //{{{_checkID
    /**
     * @access private
     */
    
function _checkID($funcName$id)
        {
        if (!
assert ('isset($this->_elements[$id])'))
            {
            
$dbt debug_backtrace();
            
$errorMsg 'Assertion failed in call to XHTMLDoc::'.$funcName.':<br>';
            
$errorMsg .= 'There is no document element with the id '.$id.'<br>';
            
$errorMsg .= 'This method was called from '.$dbt[1]['file'].':'.$dbt[1]['line'];
            
$errorMsg .= '<p/>Available elements are:<pre>';
            echo 
$errorMsg;
            if (
is_array($this->_elements))
                
print_r(array_keys($this->_elements));
            echo 
'</pre>';

            
$this->_printStackTrace$dbt );
            return 
0;
            }
        return 
1;
        }
//}}}end function _checkID

    //{{{_checkAssert
    /**
     * @access private
     */
    
function _checkAssert($funcName$assert$msg$param null)
        {
        if (!
assert ($assert))
            {
            
$dbt debug_backtrace();
            
$errorMsg 'Assertion failed in call to XHTMLDoc::'.$funcName.':<br>';
            
$errorMsg .= 'This method was called from '.$dbt[1]['file'].':'.$dbt[1]['line'];
            
$errorMsg .= '<br/>'.$msg.'<p/>';
            echo 
$errorMsg;

            
$this->_printStackTrace$dbt );
            return 
0;
            }
        return 
1;
        }
//}}}end function _checkAssert

    //{{{_printStackTrace
    /**
     * Prints a stacktrace of the callstack
     *
     * @access private
     */
    
function _printStackTrace$dbt )
        {
        if (
$dbt === null)
            
$dbt debug_backtrace();
        echo 
'Complete trace of callstack:<br/>';
        
array_shift($dbt);
        foreach(
$dbt as $eachCall)
            {
            echo 
$eachCall['function'].' called from '.$eachCall['file'].':';
            echo 
$eachCall['line'].'<br/>';
            }
        echo 
'<p/>';
        }
//}}}end function _printStackTrace
    
}

if (!
function_exists('debug_backtrace'))
    {
    function 
debug_backtrace( )
        {
        
$arr = array('function' => __FUNCTION__,
                     
'line' => __LINE__,
                     
'file' => __FILE__,
                     
'class' => __CLASS__,
                     
'type' => 'Undefined',
                     
'args' => 'Undefined');
        return array(
$arr$arr);
        }
    }
?>