Location: PHPKode > projects > Aukyla Platform > aukyla/base/XHTML.php
<?php
/*
     XHTML.php, XHTML utility functions.
     Copyright (C) 2004-2005 Arend van Beelen, Auton Rijnsburg

     This program is free software; you can redistribute it and/or modify it
     under the terms of the GNU General Public License as published by the Free
     Software Foundation; either version 2 of the License, or (at your option)
     any later version.

     This program is distributed in the hope that it will be useful, but WITHOUT
     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
     more details.

     You should have received a copy of the GNU General Public License along
     with this program; if not, write to the Free Software Foundation, Inc.,
     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

     For any questions, comments or whatever, you may mail me at: hide@address.com
*/

require_once('String.php');
require_once('Widgets.php');

/**
 * @internal
 */
class XHTMLTag
{
	public function __construct($name, $single, $visible, $textAllowed, $attributes = array(), $allowedParents = array(), $requiredAttributes = array())
	{
		$this->name = $name;
		$this->single = $single;
		$this->visible = $visible;
		$this->textAllowed = $textAllowed;
		$this->attributes = $attributes;
		$this->allowedParents = $allowedParents;
		$this->requiredAttributes = $requiredAttributes;
	}

	public $name;
	public $single;
	public $visible;
	public $textAllowed;
	public $attributes;
	public $allowedParents;
	public $requiredAttributes;
}

/**
 * @brief Provides some utility functions for working with XHTML documents.
 */
class XHTML
{
	/**
	 * Returns the XHTML 1.1 Doctype Declaration.
	 *
	 * @return A string containing the XHTML 1.1 Doctype Declaration (without
	 *         newline).
	 */
	public static function doctype()
	{
		return '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
	}

	/**
	 * Converts a loose HTML document to an XHTML 1.1 document.
	 *
	 * @param document A string containing the original HTML document.
	 * @param root     This optional parameter will be filled with the root node
	 *                 of the resulting DOM document.
	 * @return A @p DOMDocument tree containing the XHTML 1.1 document.
	 */
	public static function html2XhtmlDocument($document, &$root = NULL)
	{
		self::initializeTags();

		$tagStack = '/html';
		$readingTag = false;
		$readingComment = false;
		$readingEntity = false;
		$numberedEntity = false;
		$tagOffset = 0;
		$entityOffset = 0;
		$parentNodes = array();
		$currentParent = 0;
		$currentNode = NULL;

		$xhtmlDocument = new DOMDocument("1.0");
		$root = $xhtmlDocument->createElement('html');
		$root->setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
		$xhtmlDocument->appendChild($root);

		$parentNodes[$currentParent] = $root;

		for($i = 0; $i < strlen($document); $i++)
		{
			// currently reading text
			if($readingTag == false)
			{
				// currently reading an entity
				if($readingEntity == true)
				{
					if($document{$i} == "#")
					{
						$numberedEntity = true;
					}
					elseif($document{$i} == ';' || $document{$i} == ' ' ||
					       $document{$i} == '<' || $document{$i} == '>' ||
					       $document{$i} == '&')
					{
						if($currentNode == null)
						{
							$currentNode = $xhtmlDocument->createTextNode('');
						}

						if($i - $entityOffset == 1 ||
						   substr($document, $entityOffset, $i - $entityOffset - 1) == '&amp')
						{
							$currentNode->appendData('&');
						}
						elseif($numberedEntity == true)
						{
							$currentNode->appendData(chr((int) substr($document, $entityOffset + 2, $i - $entityOffset - 2)));
						}
						$readingEntity = false;
					}
				}
				else
				{
					// a tag starts
					if($document{$i} == '<')
					{
						if($currentNode != null)
						{
							$parentNodes[$currentParent]->appendChild($currentNode);
							$currentNode = null;
						}

						$readingTag = true;
						$tagOffset = $i;
					}
					// an entity starts
					elseif($document{$i} == '&')
					{
						$readingEntity = true;
						$numberedEntity = false;
						$entityOffset = $i;
					}
					// reading a text node
					else
					{
						if($currentNode == null)
						{
							$currentNode = $xhtmlDocument->createTextNode('');
						}

						$currentNode->appendData($document{$i});
					}
				}
			}
			// currently reading comments
			elseif($readingComment == true)
			{
				if(substr($document, $i - 2, 3) == '-->')
				{
					$readingComment = false;
				}
			}
			// currently reading a tag
			else
			{
				// see if we start reading comments
				if($i == $tagOffset + 3 &&
				   substr($document, $tagOffset, 4) == '<!--')
				{
					$readingComment = true;
					continue;
				}

				// the tag is finished
				if($document{$i} == '>')
				{
					$readingTag = false;
					$tag = substr($document, $tagOffset, $i - $tagOffset + 1);
					preg_match('/^<\s*(\/?\w+).*>$/', $tag, $matches);
					$tagName = strtolower($matches[1]);

					// ending tag
					if(String::startsWith($tagName, '/'))
					{
						if($currentParent > 0 &&
						   (String::endsWith($tagStack, $tagName) || strpos($tagStack, "$tagName/") !== false))
						{
							$tagStack = String::substringBefore($tagStack, $tagName, true);
							$currentParent = substr_count($tagStack, '/') - 1;
						}
						if($tagName == '/html')
						{
							break;
						}
					}
					// starting tag
					else if($tagName != 'html')
					{
						if(isset(self::$tags[$tagName]) == false)
						{
							continue;
						}

						$tagStruct = self::$tags[$tagName];

						// check whether the parent tag is valid
						if(in_array(String::substringAfter($tagStack, '/', true),
						            $tagStruct->allowedParents) == false)
						{
							$ancestors = array_reverse(explode('/', String::substringBefore($tagStack, '/', true)));

							$parentFound = false;
							$previousAncestor = String::substringAfter($tagStack, '/', true);
							foreach($ancestors as $ancestor)
							{
								if(in_array($ancestor, $tagStruct->allowedParents))
								{
									$tagStack = String::substringBefore($tagStack, "/$previousAncestor", true);
									$currentParent = substr_count($tagStack, '/') - 1;

									$parentFound = true;
								}
								$previousAncestor = $ancestor;
							}

							if($parentFound == false)
							{
								foreach($tagStruct->allowedParents as $parent)
								{
									$parentStruct = self::$tags[$parent];
									if(in_array(String::substringAfter($tagStack, '/', true),
											$parentStruct->allowedParents) == true)
									{
										$currentNode = $xhtmlDocument->createElement($parent);
										$parentNodes[$currentParent]->appendChild($currentNode);

										$currentParent++;
										$parentNodes[$currentParent] = $currentNode;
										$tagStack .= "/$parent";

										$currentNode = null;
										$parentFound = true;
									}
								}
							}

							if($parentFound == false)
							{
								continue;
							}
						}

						$attributes = array();
						while(preg_match('/^<\s*\/?\w+\s+(\w+=(?:".*?"|[^" >]+)).*>$/', $tag, $matches))
						{
							$attributes[] = $matches[1];
							$tag = str_replace($matches[1], '', $tag);
						}

						$currentNode = $xhtmlDocument->createElement($tagName);

						$attributeKeys = array();
						foreach($attributes as $attribute)
						{
							$key = strtolower(String::substringBefore($attribute, '='));
							$value = String::substringAfter($attribute, '=');
							$attributeKeys[] = $key;

							if(in_array($key, $tagStruct->attributes) == false &&
								in_array($key, $tagStruct->requiredAttributes) == false)
							{
								continue;
							}

							if($value{0} == '"')
							{
								$value = substr($value, 1);
							}
							if(substr($value, -1) == '"')
							{
								$value = substr($value, 0, strlen($value) - 1);
							}

							$currentNode->setAttribute($key, $value);
						}
						foreach($tagStruct->requiredAttributes as $key)
						{
							if(in_array($key, $attributeKeys) == false)
							{
								$currentNode->setAttribute($key, '');
							}
						}

						$parentNodes[$currentParent]->appendChild($currentNode);

						if($tagStruct->single == false)
						{
							$currentParent++;
							$parentNodes[$currentParent] = $currentNode;
							$tagStack .= "/$tagName";
						}

						$currentNode = null;
					}
				}
			}
		}

		return $xhtmlDocument;
	}

	/**
	 * Converts a loose HTML document to an XHTML 1.1 document.
	 *
	 * @param document A string containing the original HTML document.
	 * @return A string containing the complete XHTML 1.1 document.
	 */
	public static function html2Xhtml($document)
	{
		$xhtmlDocument = self::html2XhtmlDocument($document, $root);

		return self::doctype()."\n".
		       $xhtmlDocument->saveXML($root)."\n";
	}

	private static function initializeTags()
	{
		if(isset(self::$tags))
		{
			return;
		}

		self::$tags['html']  = new XHTMLTag('html',  false, true,  true);

		self::$tags['head']  = new XHTMLTag('head',  false, true,  false, array('style'),                       array('html'));
		self::$tags['title'] = new XHTMLTag('title', false, true,  true,  array('style'),                       array('head'));
		self::$tags['link']  = new XHTMLTag('link',  true,  true,  false, array('style', 'href', 'rel', 'type'),array('head'));
		self::$tags['meta']  = new XHTMLTag('meta',  true,  true,  false, array('style', 'http-equiv', 'name'), array('head'), array('content'));

		self::$tags['body']  = new XHTMLTag('body',  false, true,  true,  array('style'), array('html'));
		self::$tags['h1']    = new XHTMLTag('h1',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['h2']    = new XHTMLTag('h2',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['h3']    = new XHTMLTag('h3',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['h4']    = new XHTMLTag('h4',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['h5']    = new XHTMLTag('h5',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['h6']    = new XHTMLTag('h6',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['p']     = new XHTMLTag('p',     false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div'));
		self::$tags['a']     = new XHTMLTag('a',     false, true,  true,  array('style', 'href', 'name'),       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p',      'span', 'strong', 'em'));
		self::$tags['div']   = new XHTMLTag('div',   false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['span']  = new XHTMLTag('span',  false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['strong']= new XHTMLTag('strong',false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['em']    = new XHTMLTag('em',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['br']    = new XHTMLTag('br',    true,  true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['hr']    = new XHTMLTag('hr',    true,  true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'));
		self::$tags['img']   = new XHTMLTag('img',   true,  true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'a', 'span', 'strong', 'em'), array('src', 'alt'));
		self::$tags['table'] = new XHTMLTag('table', false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p',      'span', 'strong', 'em'));
		self::$tags['thead'] = new XHTMLTag('thead', false, true,  true,  array('style'),                       array('table'));
		self::$tags['tbody'] = new XHTMLTag('tbody', false, true,  true,  array('style'),                       array('table'));
		self::$tags['tfoot'] = new XHTMLTag('tfoot', false, true,  true,  array('style'),                       array('table'));
		self::$tags['tr']    = new XHTMLTag('tr',    false, true,  true,  array('style'),                       array('table', 'thead', 'tbody', 'tfoot'));
		self::$tags['td']    = new XHTMLTag('td',    false, true,  true,  array('style', 'colspan', 'rowspan'), array('tr'));
		self::$tags['ol']    = new XHTMLTag('ol',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p',      'span', 'strong', 'em'));
		self::$tags['ul']    = new XHTMLTag('ul',    false, true,  true,  array('style'),                       array('body', 'td', 'li', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p',      'span', 'strong', 'em'));
		self::$tags['li']    = new XHTMLTag('li',    false, true,  true,  array('style'),                       array('ul', 'ol'));

		self::$tags['b'] = self::$tags['strong'];
		self::$tags['i'] = self::$tags['em'];

		//self::$tags['script']= new XHTMLTag('script',false, false, false, array(), array('head'));
		//self::$tags['style'] = new XHTMLTag('style', false, false, false, array(), array('head'));
	}

	private static $tags;
}

/**
 * @brief This widget can be used to insert arbitrary HTML contents.
 *
 * The contents you supply will be automatically converted to valid XHTML code
 * and possibly offending tags will be removed.
 *
 * @sa XHTML
 */
class HTMLWidget extends RawWidget
{
	/**
	 * Constructor.
	 *
	 * @param parent   Parent container to add this widget to.
	 * @param contents The HTML contents to show.
	 */
	public function __construct(Container $parent, $contents)
	{
		$domDocument = XHTML::html2XhtmlDocument("<html><body>$contents</body></html>");

		$xPath = new DOMXPath($domDocument);

		$nodeList = $xPath->query('/html/body');

		$html = substr($domDocument->saveXML($nodeList->item(0)), 6, -7);

		parent::__construct($parent, "<htmlblock xmlns=\"http://www.w3.org/1999/xhtml\"><span>$html</span></htmlblock>");
	}
}

?>
Return current item: Aukyla Platform