Location: PHPKode > projects > OpenWolf Guidelines Validator > openWolf 0.9.9/parse.core.class.php
<?php

/*************************************************

openWolf - an HTML accessibility guidelines validator
Author: Geoff Munn (hide@address.com)
Version: 0.9.9

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.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

You may contact the author of openWolf by e-mail at: hide@address.com

The latest version of openWolf can be obtained from:
http://openwolf.sourceforge.net/

*************************************************/

define('LOOP_LENGTH', 60);

define('HIGHLIGHT_NONE', 0);
define('HIGHLIGHT_ELEMENT', 1);
define('HIGHLIGHT_ATTRIBUTE', 2);
define('HIGHLIGHT_VALUE', 3);
define('HIGHLIGHT_ELEMENT_INCLUDE_CONTENTS', 4);
define('HIGHLIGHT_ELEMENT_TEXT', 5);
define('HIGHLIGHT_ATTRIBUTE_INCLUDE_CONTENTS', 6);
define('HIGHLIGHT_ELEMENT_INCLUDE_TEXT', 7);
define('HIGHLIGHT_ELEMENT_INCLUDE_TEXT_NOT_CHILDREN', 8);
define('HIGHLIGHT_CSS', 9);
define('HIGHLIGHT_VALUE_INCLUDE_CONTENTS', 10);

define('ELEMENT_END_TAG_REQUIRED', 0);
define('ELEMENT_END_TAG_OPTIONAL', 1);
define('ELEMENT_END_TAG_FORBIDDEN', 2);

define ('PARSE_BLOCK_ELEMENT', 1);
define ('PARSE_INLINE_ELEMENT', 2);

define('DEFAULT_SCREEN_WIDTH', 800);

class Parse_core {
	public $currentPage;						//This is the location of the page being checked
	public $allElements=Array();				//Holds the DOM, accessed sometimes from outside of this class
	public $bodyInnerText;						//All the inner text of the body element
	public $_imported_stylesheets=Array();		//We put stylesheest from previous scans here.
	
	public $_full_string;						//Holds the entire page that has been parsed in one long string
	private $_baseUrl;							//Used for resolving relative links
	private $_links=Array(); 					//This is a lookup table of the links we've resolved so far
	private $_docType;							//Holds the doctype for this page		
	private $_docTypeDetails=Array();			//This holds the 'bits' of the doctype	
	private $_defaultPage;						//This is the default page type (ie, index.html) that is appended to resolved urls
	private $_domain;							//This is the domain of the current page being checked
	private $_childElements=Array();			//This is a cached store of the child elements of any particular element
	public $_styles=Array();					//VERY IMPORTANT array which holds all the stylesheet information
	private $_property_lookup=Array();			//Used to identify styles by their properties
	private $_selector_lookup=Array();			//Used to quickly identify styles by their selector
	private $_styles_lookup=Array();			//This matches styles to elements by elementIndex (used in getStyles)
	private $_styles_hash_lookup=Array();		//This is a hashed version of the _styles_lookup table (used in getStyles)
	private $_attribute_lookup=Array();			//Used to identify HTML elements by their attributes
	private $_element_property_lookup=Array();	//This returns the style value for a property of a particular element (not being used?)
	private $_computed_styles_lookup=Array();	//This stores the computed style for a particular element and property combination
	private $_element_lookup=Array();			//This holds a list of all the used HTML elements and their element indices
	private $_styles_value_lookup=Array();		//This holds the resolved style listing for an element (used in getStyleValue)
	private $_stylesheet_lookup=Array();		//This is a lookup table of all the parsed stylesheets to prevent infinite recursion
		
	
	/*This is the main function.  It takes the HTML as a string and turns it into an array of elements, which can then be accessed by the other functions listed here.  This MUST be run for this class to work.
	If you know you do not need to use anything involve the parent elements or closing elements, then you can specify $get_parents=false.  $get_attributes should always be specified as true.*/
	function parse_HTML($contents, $get_parents=true, $get_attributes=true){
		//This will create an array of all the elements:
		
		//REMEMBER! If you only need a simple array of elements, not their relationships to each other (ie parents 
		//and so forth), then specify get_parents=false
		
		$contents=str_replace("\t", '    ', $contents);
		$contents=str_replace("\r", "\n", $contents);
		
		$this->_full_string=$contents;
		$rows=explode("\n", $contents);
		$count=0;
		$sourceCount=0;
		$isScript=false;
		$isStyle=false;
		$lineNumber=1;
		$lineBreaks=0;
		$col=1;
		$lastBreak=0;
		
		//These are all the attributes we are SPECIFICALLY keeping track of for the purpose of this validator...
		$this->_attribute_lookup=Array();
		$this->_attribute_lookup['style']=Array();
		$this->_attribute_lookup['onmousedown']=Array();
		$this->_attribute_lookup['onmouseup']=Array();
		$this->_attribute_lookup['onclick']=Array();
		$this->_attribute_lookup['onmouseover']=Array();
		$this->_attribute_lookup['onmouseout']=Array();
		$this->_attribute_lookup['onmousemove']=Array();
				
		$in_block_element=false;
		for($i=0;$i<strlen($contents);$i++){
			$element=get_element($contents, $i, $isScript, $isStyle);
			//how many line breaks?
			$lineBreaks=substr_count($element, "\n");
			if($lineBreaks==0){
				$col=$i-$lastBreak;
				if($col==0)
					$col=1;
				
			} else {
				$lastBreak=$i;
				$col=1;
			}
			$lineNumber+=$lineBreaks;
			
			//we need to filter out anything within a script element because <!-- elements will be picked up
			//This can be extended to any element that we want to ignore under certain conditions
			if(is_element($element) && !$isScript && !$isStyle){
				$tagname=tag_name($element);
				
				$is_closing_element=is_closing_element($element);
				$is_self_closing_element=is_self_closing_element($element);
				
				//NOTE: If this list of attributes changes, update the functions that use these:
				$this->allElements[$count]['full_element']=$element;
				$this->allElements[$count]['tagname']=$tagname; 
				$this->allElements[$count]['is_closing']=$is_closing_element;
				$this->allElements[$count]['is_self_closing']=$is_self_closing_element;
				$this->allElements[$count]['line']=$lineNumber;
				$this->allElements[$count]['column']=$col;
				
				$this->allElements[$count]['elementIndex']=$count;
				$this->allElements[$count]['stringIndex']=$i;
								
				if(!$get_parents)
					$this->allElements[$count]['parents_checked']=false;
				else
					$this->allElements[$count]['parents_checked']=true;	

				//We don't want to check the attributes of closing elements			
				if($this->allElements[$count]['is_closing']==false){
					$this->allElements[$count]['end_tag_required']=end_tag_required($tagname);
					
					if($get_attributes){
						$this->allElements[$count]['attributes']=get_attributes($element);
						$this->allElements[$count]['attributes_checked']=true;
						//add any inline styles to the attribute lookup table
						
						foreach($this->_attribute_lookup as $this_required_attribute => $key){
							if(isset($this->allElements[$count]['attributes'][$this_required_attribute]))
								if(!in_array($count, $this->_attribute_lookup[$this_required_attribute]))
									$this->_attribute_lookup[$this_required_attribute][]=$count;
						}	
								
					} else
						$this->allElements[$count]['attributes_checked']=false;
					
					$this->allElements[$count]['sourceIndex']=$sourceCount;
					
					//For lookup purposes in other functions:
					$this->_element_lookup[$tagname][]=$count;
					
					//some commonly used attributes:
					//DEPRECATED:  These should not be used in future applications
					//$this->allElements[$count]['id']=@$this->allElements[$count]['attributes']['id']['value'];
					//$this->allElements[$count]['class']=@$this->allElements[$count]['attributes']['class']['value'];
					//$this->allElements[$count]['style']=@$this->allElements[$count]['attributes']['style']['value'];
					
					$sourceCount++;
				} else {
					$this->allElements[$count]['sourceIndex']=-1;
					$this->allElements[$count]['attributes_checked']=true;
				}
				
				if($this->allElements[$count]['tagname']=='script' && $this->allElements[$count]['is_closing']==false)
					$isScript=true;
				
				if($this->allElements[$count]['tagname']=='style' && $this->allElements[$count]['is_closing']==false)
					$isStyle=true;
								
				$count++;
			} else {
				if($isScript)
					$isScript=false;
				
				if($isStyle)
					$isStyle=false;
								
				if(!$isScript && !$isStyle){
					if($count-1>=0)
						$this->allElements[$count-1]['text']=$element;
				}
			}
					
			$i+=strlen($element)-1;
			if($i>strlen($contents))
				break;	
		}
		
		if($get_parents)
			get_parent_elements($this->allElements);	
	}
	
	/*This sets the base url of the current page, which is used for constructing the resolved URLs of href and src attribute values.  If this is not set, then you may not be able to retrieve the href or src attribute values.*/
	function setBaseUrl($base){
		$this->_baseUrl=trim(strtolower($base));
	}
	
	/*This returns the baseUrl value which should be set in order for the href and src functions to work.*/
	function getBaseUrl(){
		return $this->_baseUrl;
	}
	
	/*This stores the current page location, which is used in a number of functions.*/
	function setCurrentPage($current){
		$this->currentPage=trim(strtolower($current));
	}
	
	/*This returns the page that is currently in memory.*/
	function getCurrentPage(){
		return $this->currentPage;
	}
	
	/*This returns the default page (ie, index.html) that the site is using.
	This is mainly used by internal functions.
	If $value=-1 then the current value is returned, otherwise the value becomes the defaultPage value.*/
	function defaultPage($value=-1){
		if($value===-1)
			return $this->_defaultPage;
		else
			$this->_defaultPage=trim(strtolower($value));
	}
	
	/*This returns a string with every element concatenated into one value.*/
	function get_html(){
		$html='';
		$elements=$this->allElements;
		
		foreach($elements as $this_element)
			$html.=$this_element['full_element'];

		return $html;
	}
	
	/*This returns an array of elements matching this tag name.*/
	function getElementsByTagname($tagname){
		$tagname=strtolower($tagname);
		
		if(isset($this->_element_lookup[$tagname]))
			return $this->_element_lookup[$tagname];
		else return Array();
	}
	
	/*This returns a list of elementIndexes which contain this attribute.  Matches include attributes with empty values.*/
	function getElementsByAttribute($attribute){
		//This relies of the attribute being listed in the _attribute_lookup table
		
		if(isset($this->_attribute_lookup[$attribute]))
			return $this->_attribute_lookup[$attribute];
		else return false;
	}
	
	function getElementById($id){
		//Note: this returns an array of possibly many elements, when usually only
		//one is expected.

		$results=Array();
		$count=0;
		for($i=0; $i<count($this->allElements); $i++){	
			if($this->allElements[$i]['is_closing']==false){
				if(isset($this->allElements[$i]['attributes']['id']['specified'])){	
					if($this->allElements[$i]['attributes']['id']['value']==$id){	
						$results[$count]=$this->allElements[$i]['elementIndex'];
						$count++;
					}
				}
			}
		}
		return $results;
	}
	
	/*This returns the value of an attribute for a specific element.*/
	function getAttribute($elementIndex, $attribute, $lowercase=false){
		//Get an attribute of an element from the collection
		if(!isset($this->allElements[$elementIndex]))
			return '';
				
		if($this->allElements[$elementIndex]['attributes_checked']==false){
			$this->allElements[$elementIndex]['attributes']=get_attributes($this->allElements[$elementIndex]['full_element']);
			$this->allElements[$elementIndex]['attributes_checked']=true;
			
			foreach($this->_attribute_lookup as $this_required_attribute => $key){
				if(isset($this->allElements[$elementIndex]['attributes'][$this_required_attribute]))
					if(!in_array($elementIndex, $this->_attribute_lookup[$this_required_attribute]))
						$this->_attribute_lookup[$this_required_attribute][]=$elementIndex;
			}
		}
		
		if(isset($this->allElements[$elementIndex]['attributes'][$attribute]['specified'])){
			if($lowercase)
				return strtolower($this->allElements[$elementIndex]['attributes'][$attribute]['value']);	
			else
				return $this->allElements[$elementIndex]['attributes'][$attribute]['value'];	
		} else 
			return '';
		
	}
	
	/*This is an experimental function which lets you change the value of an attribute.  The rebuild_element function is called durin this so that the start and stop positions of the element are updated.*/
	function setAttribute($thisElement, $attribute='', $value=''){
		//WARNING: experimental only!
		if($this->allElements[$thisElement]['attributes_checked']==false){
			$this->allElements[$thisElement]['attributes']=get_attributes($this->allElements[$thisElement]['full_element']);
			$this->allElements[$thisElement]['attributes_checked']=true;
			
			foreach($this->_attribute_lookup as $this_required_attribute => $key){
				if(isset($this->allElements[$thisElement]['attributes'][$this_required_attribute]))
					if(!in_array($thisElement, $this->_attribute_lookup[$this_required_attribute]))
						$this->_attribute_lookup[$this_required_attribute][]=$thisElement;
			}
			
		}
		if($attribute!=''){
			$this->allElements[$thisElement]['attributes'][$attribute]['name']=$attribute;
			$this->allElements[$thisElement]['attributes'][$attribute]['value']=$value;
			$this->allElements[$thisElement]['attributes'][$attribute]['specified']=true;
			$this->rebuild_element($thisElement);
		}
	}
	
	/*Another experimental function, you can change the text of an element.  How this affects other elements has not been investigated.*/
	function setText($thisElement, $text){
		//Question: does this seriously warp any statistics, and if so what?
		$this->allElements[$thisElement]['text']=$text;
	}
	
	/*This is an experimental function that needs to be called if you use setAttribute to change something.  We need to recalculate the position of everything.  I do not recommend that you use setAttribute because I haven't really tested it widely.*/
	function rebuild_element($thisElement){
		//used after an attribute's value has changed via setAttribute:
		if(isset($this->allElements[$thisElement]['attributes'])){
			$attributes=$this->allElements[$thisElement]['attributes'];
			
			$element_string='<' . $this->allElements[$thisElement]['tagname'];
			if(count($attributes)>0){
				$element_string .= ' ';
				foreach($attributes as $this_attribute)
					$element_string.=$this_attribute['name'] . "=\"" . $this_attribute['value'] . "\" ";
				
				$element_string=trim($element_string);
			}
			$element_string.='>';
			
			//we need to rebuild the attributes array so that the string positions are correct:
			$this->allElements[$thisElement]['attributes']=get_attributes($element_string);
			$this->allElements[$thisElement]['full_element']=$element_string;
			
			foreach($this->_attribute_lookup as $this_required_attribute => $key){
				if(isset($this->allElements[$thisElement]['attributes'][$this_required_attribute]))
					if(!in_array($thisElement, $this->_attribute_lookup[$this_required_attribute]))
						$this->_attribute_lookup[$this_required_attribute][]=$thisElement;
			}
		}
	}
	
	/*This returns the complete array containing all the attributes (if any) for a particular element ($elementIndex).*/
	function getAttributes($elementIndex){
		$temp=Array();
		$temp=$this->allElements[$elementIndex]['attributes'];
		return $temp;
	}
	
	/*This returns the lowercased tag name for the supplied $elementIndex.*/
	function tagname($elementIndex){
		$tagname=@$this->allElements[$elementIndex]['tagname'];
		
		return $tagname;
	}
	
	/*This returns all the HTML contained within this element and its closing tag.*/
	function innerHTML($elementIndex, $strip_comments=false){
		//Get the string index of the provided element and the string index of the closing element associated with this element.
		
		if($this->allElements[$elementIndex]['is_self_closing']==true || !isset($this->allElements[$elementIndex]['closingIndex']))
			return '';
			
		$opening=$this->allElements[$elementIndex]['stringIndex'];
		$closing=$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['stringIndex'];
		$closing+=strlen($this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element']);

		$html=substr($this->_full_string, $opening, $closing-$opening);
		$html=substr($html, strlen($this->allElements[$elementIndex]['full_element']));
		$html=substr($html, 0, -strlen($this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element']));
		
		if($strip_comments){
			$search = array('@<![\s\S]*?--[ \t\n\r]*>@'        // Strip multi-line comments including CDATA
			);
			
			$html = preg_replace($search, '', $html);
		}
		
		return $html;
		
	}
	
	/*This returns all the HTML contained within this element and its closing tag, including the original element itself.
	If the $element_limit value is otherwise specified, then only text up to that number will be returned.*/
	function outerHTML($elementIndex, $element_limit=-1){
		//return the element, its contents and it's closing tag
		
		if(!isset($this->allElements[$elementIndex]['closingIndex']))
			return '';
				
		if($this->allElements[$elementIndex]['is_self_closing']==true)
			return $this->allElements[$elementIndex]['full_element'];
			
		$opening_element=$this->allElements[$elementIndex]['full_element'];
		$closing_element=$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element'];
		
		if($element_limit==-1)
			$inner=$this->innerHTML($elementIndex);
		else {
			//we only want to show the first few elements contained within this particular element...
			$i=$elementIndex+1;
			$inner=@$this->allElements[$elementIndex]['text'];
			$tag_count=0;
			$aborted=false;
			while($i<$this->allElements[$elementIndex]['closingIndex']){
				$inner.=$this->allElements[$i]['full_element'] . @ $this->allElements[$i]['text'];
				
				if(!$this->isClosing($i))
					$tag_count++;
					
				if($tag_count==$element_limit){
					$aborted=true;
					break;
				}
					
				$i++;
								
			}
			if($aborted)
				$inner.="...\n";
		}
		return $opening_element . $inner . $closing_element;
	}
	
	/*This returns all the text contained within this element and its closing tag.*/
	function innerText($elementIndex, $ignore_elements=Array(), $replace_elements=Array()){
		//Get the string index of the provided element and the string index of the closing element
		//associated with this element
		
		//NOTE: some closing elements may return errors if the innertext value is attempted
		
		if($this->allElements[$elementIndex]['is_self_closing']==true || !isset($this->allElements[$elementIndex]['closingIndex']))
			return '';
		
		//Sometimes orphans may sneak through here...
		if($this->allElements[$elementIndex]['closingIndex']==-1)
			return '';
			
		//$opening=$this->allElements[$elementIndex]['stringIndex'];
		//$closing=$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['stringIndex'];
		//$closing+=strlen($this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element']);
		//$inner=substr($this->full_string, $opening, $closing-$opening);
		//echo 'the old inner text is ' . $inner  . '<br>';
		$inner='';
		for($i=$elementIndex; $i<$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['elementIndex']; $i++){
			//We need to go through all the $ignore_elements and check that this $i is either not directly listed, and is not
			//a child of any listed element index
			if(sizeof($ignore_elements)==0)
				$inner.=$this->allElements[$i]['full_element'] . @$this->allElements[$i]['text'];
			else {
				$add=true;
				foreach($ignore_elements as $this_element){
					if(is_numeric($this_element)){
						if(in_array($i, $ignore_elements) || $this->isParent($this_element, $i)){
							$add=false;
							break;
						}
					}
				}
				if($add){
					$inner.=$this->allElements[$i]['full_element'] . @$this->allElements[$i]['text'];
				}
			}
		}
		
		//Ignore elements will remove the contents of any element in this array.
		//For instance, you do not want any scripts or stylesheets in a div element to be returned
		//as part of the innerText result
		$search = Array('@<script[^>]*?>.*?</script>@si',  // Strip out javascript
               '@<style[^>]*?>.*?</style>@siU',    // Strip style tags properly
               '@<![\s\S]*?--[ \t\n\r]*>@'        // Strip multi-line comments including CDATA
		);
		
		foreach($ignore_elements as $this_element)
			if(!is_numeric($this_element))
				$search[]='@<' . $this_element . '[^>]*?>.*?</' . $this_element . '>@siU';
        
		/*
		'@<legend[^>]*?>.*?</legend>@siU'    // Strip legend elements
		*/
		$inner = preg_replace($search, '', $inner);

		//Replace elements will replace any element with an alternative, usually a space.
		//This does not replace the contents.  It's useful to prevent words from being joined
		//together when spans or line breaks are removed.
		$replace=Array();
		foreach($replace_elements as $this_element => $replacement){
				$replace[0]='@<' . $this_element . '[^>]*?>@siU';
				$replace[1]='@</' . $this_element . '[^>]*?>@siU';
				$replace[2]='@<' . $this_element . ' /[^>]*?>@siU';
				
				$inner = preg_replace($replace, $replacement, $inner);
		}
					
		return strip_tags($inner);
	}
	
	/*I really can't think of a use for this...*/
	function outerText($elementIndex){
		//This returns the text of an element (elementIndex) with the surrounding element attached.
		//This should only be used for display purposes unless you have both eyes wide open
		
		//TODO:		Does this script ignore loop actually work? I have my doubts... this also applies to innerText
		
		if($this->allElements[$elementIndex]['is_self_closing']==true || !isset($this->allElements[$elementIndex]['closingIndex']))
			return '';
				
		$opening=$this->allElements[$elementIndex]['stringIndex'];
		$closing=$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['stringIndex'];
		$closing+=strlen($this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element']);
		$inner=substr($this->_full_string, $opening, $closing-$opening);
		$innerCopy=strtolower($inner);
		
		//remove any <script> elements
		//NOTE: This assumes that scripts always have closing elements
		$start=strpos($innerCopy, '<script');
		
		while($start<>''){
			$end=strpos($innerCopy, '</script>');
			$inner=substr_replace($inner, '', $start, $end-$start+9);
			
			$innerCopy=strtolower($inner);
			$start=strpos($innerCopy, '<script');
		}
		
		$opening_element=$this->allElements[$elementIndex]['full_element'];
		$closing_element=$this->allElements[$this->allElements[$elementIndex]['closingIndex']]['full_element'];
		
		return $opening_element . strip_tags($inner) . $closing_element;
	}
	
	/*This will return the complete text contained within the first body element.
	There is the potential for formatting to be lost since all the tags will be stripped.*/
	function bodyInnerText(){
		if($this->bodyInnerText==''){
			//NOTE: if there is no body element, it will always check for one
			//Possibly create a -1 default value and check for that?
			$body=$this->getElementsByTagname('body');
			if(count($body)>0){
				if($body[0]!=''){
					$innerText=$this->innerText($body[0]);
					$this->bodyInnerText=$innerText;
					return $innerText;
				} else
					return '';
			} else
				return '';
		} else
			return $this->bodyInnerText;
	}
	
	function parentElement($elementIndex){
		
		if($this->allElements[$elementIndex]['parents_checked']==false){
			get_parent_elements($this->allElements, $elementIndex);
			$this->allElements[$elementIndex]['parents_checked']=true;
		}
		
		if(isset($this->allElements[$elementIndex]['parentElement']))
			return $this->allElements[$elementIndex]['parentElement'];
		else return -1;
	}
	
	/*This returns just the immediate children of an element ($elementIndex).
	If you want the complete tree, use all_descendants instead.*/
	function childElements($elementIndex){
		//get the child elements of an element
		//This only returns the immediate children of an element
		
		if($this->allElements[$elementIndex]['parents_checked']==false){
			get_parent_elements($this->allElements, $elementIndex);
			$this->allElements[$elementIndex]['parents_checked']=true;
		}
		
		if(isset($this->_childElements[$elementIndex]))
			return $this->_childElements[$elementIndex];
		else {
			$closingIndex=$this->allElements[$elementIndex]['closingIndex'];
			$children=Array();
			
			for($i=$elementIndex+1;$i<$closingIndex;$i++){
				if(!$this->allElements[$i]['is_closing'] && $this->allElements[$i]['parentElement']==$elementIndex)
					$children[]=$this->allElements[$i]['elementIndex'];
			}
			
			$this->_childElements[$elementIndex]=$children;
			return $children;
		}
		
	}
	
	/*This will return an array of all the elements contained within an element ($elementIndex).
	You can specifically return a particular type of element by specifing $tag_name, and you can ignore a
	particular element by specifing $avoid
	*/
	function all_descendants($elementIndex, $tag_name='', $avoid=''){
		//NOTE: there is no caching for these results
		//NOTE: The $avoid flag will pick up incorrectly nested elements and orphans
		//TODO:	extend $tag_name and $avoid to be arrays for extra functionality...
		
		//TODO: Notice: Undefined index: closingIndex in parse.core.class.php on line 626 for the link
		//		http://www.usatoday.com/travel/destinations/cityguides/boston/overview.htm (vault id 51232)
		//		http://www.usatoday.com/news/washington/2007-09-14-report_N.htm (vault id 65995)
		//		http://news.bbc.co.uk/1/hi/world/south_asia/country_profiles/1157960.stm
		
		if(!isset($this->allElements[$elementIndex]))
			return Array();

		//TODO: this next line occasionally springs errors: (the test above should fix it!)
		$closingIndex=$this->allElements[$elementIndex]['closingIndex'];
		$descendants=Array();
		$ignore=false;
		for($i=$elementIndex+1; $i<$closingIndex; $i++){
			
			if(!$this->allElements[$i]['is_closing']){
				if($tag_name=='')
					$descendants[]=$this->allElements[$i]['elementIndex'];
				else {
					$tagname=$this->tagname($this->allElements[$i]['elementIndex']);
					//if we do not want to get any elements within a particular tag (ie, nested tables for instance)
					//then we can skip children if the opening tag matches this avoid tag
					if($tagname==$avoid && $this->allElements[$i]['is_closing']==false)
						$ignore=true;
					
					//If this is a closing tag then we can start to include things.
					if($tagname==$avoid && $this->allElements[$i]['is_closing']==true)
						$ignore=false;
					
					if($tagname==$tag_name && $ignore==false)
						$descendants[]=$this->allElements[$i]['elementIndex'];
				}
			}
		}
		
		return $descendants;
	}
	
	function isClosing($elementIndex){
		if(isset($this->allElements[$elementIndex]))
			return $this->allElements[$elementIndex]['is_closing'];
		else
			return false;
	}
	
	/*This returns the array allElements which contains every bit of information about the current page.*/
	function getAllElements(){
		return $this->allElements;
	}
	
	/*This returns the charset encoding of the 'Content-Type' meta tag (if any is found).
	If multiple values are found, the most recent (last) one is returned.*/
	function encoding(){
		//<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
		$meta=$this->getElementsByTagname('meta');
		$encoding='';
		foreach($meta as $this_meta){
			//get the http-equiv value:
			if(isset($this->allElements[$this_meta]['attributes']['http-equiv']['value'])){
				$equiv=strtolower($this->allElements[$this_meta]['attributes']['http-equiv']['value']);
				if($equiv=='content-type'){
					//Now get the content value:
					$content=$this->allElements[$this_meta]['attributes']['content']['value'];
					//get anything right of the equals sign:
					$temp=split('=', $content);
					$encoding=trim($temp[count($temp)-1]);
				}
			}
		}
		return strtolower($encoding);
	}
	
	/*This returns the complete element in question, but not the contained text, or closing element.*/
	function fullElement($elementIndex){
		if(isset($this->allElements[$elementIndex]['full_element']))
			$full=$this->allElements[$elementIndex]['full_element'];
		else
			$full='';	
		
		return $full;
	}
	
	/*This will return the fully resolved path of an anchor.  The baseUrl value needs to be set for this to work.
	If $is_string is true, then $elementIndex can be a string.*/
	function href($elementIndex, $is_string=false, $file_location=''){
				
		if(!isset($this->_links[$elementIndex])){
			
			if($file_location=='')
				$file_location=$this->_baseUrl;
				
			if($file_location!=''){
				if(!$is_string)
					//some anchor tags may not have an href attribute
					$link=trim(@$this->allElements[$elementIndex]['attributes']['href']['value']);
				else 
					$link=$elementIndex;
				
				//if this link is to 'test.html', for instance, or './test.html', or '../html', we need to attach the directory structure to it
				if($link!=''){
					$protocol=get_protocol($link);
					if($protocol==''){
						//if this is a plain old anchor or query with NO filename, then prepend the current page:
						$char=substr($link,0,1);
						if($char=='#' || $char=='?'){
							$abs_link=$this->currentPage . $link;
							$no_page=true;
						} else {
							$abs_link=make_abs($link, $file_location);
							$no_page=false;
						}
						$abs_link=check_filename($abs_link, $this->defaultPage());
						
						$this->_links[$elementIndex]['href']=$abs_link['href'];
						
						if($no_page==false)
							$this->_links[$elementIndex]['filename']=$abs_link['filename'];
						else
							$this->_links[$elementIndex]['filename']='';
						
						return $abs_link['href'];
					} else {
						if($protocol=='http' || $protocol=='https'){
							$link=check_filename($link,$this->defaultPage(), false, $this->_domain);
							
							$this->_links[$elementIndex]['href']=$link['href'];
							$this->_links[$elementIndex]['filename']=$link['filename'];
							
							return $link['href'];
						} else
							return $link;
					}
				} else {
					$this->_links[$elementIndex]['href']='';
					$this->_links[$elementIndex]['filename']='';
					return '';
				}
			} else
				return $this->allElements[$elementIndex]['attributes']['href']['value'];
			
		} else
			return @$this->_links[$elementIndex]['href'];
	}
	
	/*This returns the fully resolved path for an image's src attribute.  The baseUrl property needs to be set for this to work.*/
	function src($elementIndex){
		//returns the absolute path, IF the baseUrl value has been set
		
		if(!isset($this->images[$elementIndex])){
			if($this->_baseUrl!=''){
				
				$link=@$this->allElements[$elementIndex]['attributes']['src']['value'];
								
				if($link!=''){
					//if this link is to 'test.html', for instance, or './test.html', or '../html', we need to attach the directory structure to it
					$protocol=get_protocol($link);
					if($protocol==''){
						$abs_link=make_abs($link, $this->_baseUrl);
						$this->_links[$elementIndex]['src']=$abs_link;
						
						return $abs_link;
					} else {
						if($protocol=='http' || $protocol=='https'){
							$this->images[$elementIndex]['src']=$link;
						}
						return $link;
					}
				} else {
					$this->images[$elementIndex]['src']='';
					return '';
				}
			} else
				return $this->allElements[$elementIndex]['attributes']['src']['value'];
		} else
			return @$this->images[$elementIndex]['src'];
	}
	
	/*This returns the filename of any href value previously calculated through the href function.*/
	function href_filename($elementIndex){
		if(isset($this->_links[$elementIndex]))
			return $this->_links[$elementIndex]['filename'];
		else
			return '';
	}
	
	/*This returns all the text fragments directly associated with this element, but not its decendants.  You can also choose to clean up the spaces found within it ($strip_spaces=true), and include the actual element as part of the result ($include_outer_element=true).*/
	function text($elementIndex, $strip_spaces=false, $include_outer_element=false){
		//This returns the text fragment associated with this element
		
		//go through each of these child elements (including the closing tags) to get the text fragment
		$text='';
		$originalIndex=$elementIndex;
		$closingIndex=@$this->allElements[$elementIndex]['closingIndex'];
		
		if(!isset($this->allElements[$elementIndex]['closingIndex']) || $closingIndex==-1)
			$text=@$this->allElements[$elementIndex]['text'];
		else {
			if($elementIndex==$closingIndex)
				$text=@$this->allElements[$elementIndex]['text'];
			else {
				while($elementIndex!=$closingIndex){
					
					//Only closing elements will have text fragments associated with them...
					if($elementIndex==$originalIndex || $this->allElements[$elementIndex]['is_self_closing']==true || ($this->isClosing($elementIndex) && $this->parentElement($elementIndex)==$originalIndex))
						$text.=@$this->allElements[$elementIndex]['text'];
					
					$elementIndex++;
					
					if($elementIndex>=sizeof($this->allElements))
						break;
				}
			}
		}
		
		if($strip_spaces)
			$text=strip_spaces($text, true);
		
		if($include_outer_element){
			$opening_element=$this->allElements[$originalIndex]['full_element'];
			$closing_element=$this->allElements[$this->allElements[$originalIndex]['closingIndex']]['full_element'];
			$text=$opening_element . $text . $closing_element;
		}
		
		return $text;
	}
	
	/*This returns true or false as to whether an attribute is specified for an element.  Empty values return true.*/
	function specified($elementIndex, $attribute){
				
		if($this->allElements[$elementIndex]['attributes_checked']==false){
			$this->allElements[$elementIndex]['attributes']=get_attributes($this->allElements[$elementIndex]['full_element']);
			$this->allElements[$elementIndex]['attributes_checked']=true;
			
			foreach($this->_attribute_lookup as $this_required_attribute => $key){
				if(isset($this->allElements[$elementIndex]['attributes'][$this_required_attribute]))
					if(!in_array($elementIndex, $this->_attribute_lookup[$this_required_attribute]))
						$this->_attribute_lookup[$this_required_attribute][]=$elementIndex;
			}
		}
		
		if(isset($this->allElements[$elementIndex]['attributes'][$attribute]['specified']))
			return $this->allElements[$elementIndex]['attributes'][$attribute]['specified'];
		else 
			return false;
	}
	
	/*This retrieves the document type on the understanding that it is somewhere ahead of the <html> element*/
	function docType(){
		//DO NOT convert this to lowercase
		//TODO: setup a default return value for instances with neither a doctype or html element
		for($i=0;$i<count($this->allElements);$i++){
			if($this->allElements[$i]['tagname']=='!doctype'){
				$this->_docType=$this->allElements[$i]['full_element'];
				$docType=Array();
				$docType['elementIndex']=$i;
				$docType['docType']=$this->allElements[$i]['full_element'];
				return $docType;
			}
			if($this->allElements[$i]['tagname']=='html')
				return '';
		}
	}
	
	/*This returns an array containing all the information about this page's document type (if any).*/
	function parse_doctype(){
		//get the docType from the current document, and parse it.
		//Then we will store this information so we don't need to figure it out again
		
		if($this->_docType==''){
			$bits=Array();
			$docType=$this->docType();
			$bits=getDoctype($docType);
			
			$this->_docTypeDetails=$bits;
			
			return $bits;
		} else
			return $this->_docTypeDetails;
	}
	
	//get the class attribute from an element
	//This ALWAYS returns a lowercase array.
	//If you want a original-case result, use getAttribute
	function className($elementIndex, $assoc=false){
		
		if(isset($this->allElements[$elementIndex]['attributes']['class']['value'])){
			$class_string=strtolower($this->allElements[$elementIndex]['attributes']['class']['value']);
			$classes=explode(' ', $class_string);
			if($assoc){
				$result=Array();
				foreach($classes as $this_class)
					$result[$this_class]=$this_class;
					
				return $result;
			} else
				return $classes;
		} else
			return '';
	}
	
	/*This returns a specially formatted string of HTML, highlighting a particular aspect.  $instance one of the $instances array entries that should be returned by a rule test.*/
	function getCode($instance){
		//get the 25 characters before the start of the element, and the next 25 characters after the start
				
		//First of all, we shall retrieve the complete row of this element:
		
		$elementIndex=$instance['elementIndex'];
		
		if($elementIndex!=-1 && $elementIndex!=-2){
			$current_element=$this->allElements[$elementIndex]['full_element'];
			$highlighted_element=stripslashes($current_element);
			$full_string=$this->_full_string;
			$start=$this->allElements[$elementIndex]['stringIndex'];
			$end=$start+strlen($current_element);
					
			//go back through the full html string until we hit a line break
			
			//Is this a simple element highlight?
			$full_code='';
			
			if($instance['highlight']==HIGHLIGHT_ELEMENT){
				//This highlights the first character, usually the opening bracket, of the element in question,
				//not including the contents of the element
				$first_char=substr($highlighted_element,0,1);
				
				$left_bit='';
				$right_bit='';
				$highlighted_element=nl2br('<span class="highlight">' . htmlspecialchars($first_char) . '</span>' . htmlspecialchars(substr($highlighted_element,1)));
			}
			
			if($instance['highlight']==HIGHLIGHT_ELEMENT_INCLUDE_CONTENTS){
				//This is the same as the previous method, but it also includes the contents of the element
				
				//For this hightlight type, we will override the normal highlighted element value:
				
				if(isset($instance['highlight_limit']))
					$highlighted_element=$this->outerHTML($elementIndex, $instance['highlight_limit']);
				else 
					$highlighted_element=$this->outerHTML($elementIndex);
					
				$first_char=substr($highlighted_element,0,1);
				
				$left_bit='';
				$right_bit='';
				$highlighted_element=nl2br('<span class="highlight">' . htmlspecialchars($first_char) . '</span>' . htmlspecialchars(substr($highlighted_element,1)));
			}
			
			if($instance['highlight']==HIGHLIGHT_ELEMENT_TEXT){
				//This highights the text of the element, including the children contained within it
				
				//For this hightlight type, we will override the normal highlighted element value:
				$highlighted_element=$this->innerText($elementIndex);
				$first_char=substr($highlighted_element,0,1);
				
				$left_bit='';
				$right_bit='';
				$highlighted_element=nl2br('<span class="highlight">' . htmlspecialchars($first_char) . '</span>' . htmlspecialchars(substr($highlighted_element,1)));
			}
			
			if($instance['highlight']==HIGHLIGHT_ATTRIBUTE){
				//This highlights an attribute of an element, not including the element's contents
				
				$attribute_start=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['attribute_start'];
				$attribute_length=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['attribute_end']-$attribute_start;
				$attribute=substr($highlighted_element, $attribute_start, $attribute_length );
				
				$left_bit=substr($highlighted_element, 0, $attribute_start);
				$right_bit=substr($highlighted_element, $attribute_start+$attribute_length);
				
				$highlighted_element=nl2br(htmlspecialchars($left_bit) . '<span class="highlight">' . htmlspecialchars($attribute) . '</span>' . htmlspecialchars($right_bit));
			}
			
			if($instance['highlight']==HIGHLIGHT_VALUE){
				//This highlights a value of an attribute of an element, not including it's contents
				
				$value_start=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['value_start'];
				$value_length=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['value_end']-$value_start;
				$value=substr($highlighted_element, $value_start, $value_length );
				
				$left_bit=substr($highlighted_element,0, $value_start);
				$right_bit=substr($highlighted_element,$value_start+$value_length);
				
				//$full_code=htmlspecialchars($string_left) . htmlspecialchars($left_bit) . '<span class="highlight" style="color:red; font-weight: bold">' . htmlspecialchars($value) . '</span>' . htmlspecialchars($right_bit) . htmlspecialchars($string_right); 
				$highlighted_element=nl2br(htmlspecialchars($left_bit) . '<span class="highlight">' . htmlspecialchars($value) . '</span>' . htmlspecialchars($right_bit));
			}
			
			if($instance['highlight']==HIGHLIGHT_ATTRIBUTE_INCLUDE_CONTENTS){
				//This highlights an attribute of an element, and includes it's contents as well.
				
				//For this hightlight type, we will override the normal highlighted element value:
				$highlighted_element=$this->outerHTML($elementIndex);
				$attribute_start=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['attribute_start'];
				$attribute_length=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['attribute_end']-$attribute_start;
				$attribute=substr($highlighted_element, $attribute_start, $attribute_length );
				
				$left_bit=substr($highlighted_element,0, $attribute_start);
				$right_bit=substr($highlighted_element,$attribute_start+$attribute_length);
				
				$highlighted_element=nl2br(htmlspecialchars($left_bit) . '<span class="highlight">' . htmlspecialchars($attribute) . '</span>' . htmlspecialchars($right_bit));
			}
			
			if($instance['highlight']==HIGHLIGHT_ELEMENT_INCLUDE_TEXT_NOT_CHILDREN){
				//This highights the text of the element, including the children contained within it
				
				//For this hightlight type, we will override the normal highlighted element value:
				$highlighted_element=$this->text($elementIndex, false, true);
				$first_char=substr($highlighted_element,0,1);
				
				$left_bit='';
				$right_bit='';
				$highlighted_element=nl2br('<span class="highlight">' . htmlspecialchars($first_char) . '</span>' . htmlspecialchars(substr($highlighted_element,1)));
			}
			
			if($instance['highlight']==HIGHLIGHT_ELEMENT_INCLUDE_TEXT){
				//This highlights the first character of an element and includes the text of all it's children as well
				
				//For this hightlight type, we will override the normal highlighted element value:
				$highlighted_element=$this->outerText($elementIndex);
				$first_char=substr($highlighted_element,0,1);
				
				$left_bit='';
				$right_bit='';
				$highlighted_element=nl2br('<span class="highlight">' . htmlspecialchars($first_char) . '</span>' . htmlspecialchars(substr($highlighted_element,1)));
			}
			
			if($instance['highlight']==HIGHLIGHT_VALUE_INCLUDE_CONTENTS){
				//This highlights the value of an element, and includes it's contents as well.
				
				//For this hightlight type, we will override the normal highlighted element value:
				$highlighted_element=$this->outerHTML($elementIndex);
				$value_start=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['value_start'];
				$value_length=$this->allElements[$elementIndex]['attributes'][strtolower($instance['highlight_attribute'])]['value_end']-$value_start;
				$value=substr($highlighted_element, $value_start, $value_length );
				
				$left_bit=substr($highlighted_element,0, $value_start);
				$right_bit=substr($highlighted_element,$value_start+$value_length);
				
				$highlighted_element=nl2br(htmlspecialchars($left_bit) . '<span class="highlight">' . htmlspecialchars($value) . '</span>' . htmlspecialchars($right_bit));
			}
			
			//this fixes up the left and right hand side bits of the current element
			if(strlen($current_element)<80){
				//we want 80 characters max
				$left_length=ceil((80-strlen($current_element))/2);
				$right_length=floor((80-strlen($current_element))/2);
			}
			
			$full_code = $highlighted_element;
			return $full_code;
		} else {
			if($instance['highlight']==HIGHLIGHT_CSS){
				
				$selector=$this->_styles[$instance['stylesheet']]['styles'][$instance['rule']][$instance['selector_group']]['selector'];
				$rule_text=$this->_styles[$instance['stylesheet']]['styles'][$instance['rule']]['rule_text'];
				
				$full_code=$selector . ' {' . $rule_text . '}';
				return nl2br(htmlspecialchars($full_code));
			} else
				return '';
		}
	}
	
	/*This returns the first 80 characters of the text ($text).*/
	function getText($text){
		if(strlen($text)>80)
			return substr($text,0,77) . '...';	
		else
			return $text;
	}
	
	/*This returns the source code row that this element starts in.*/
	function getRow($elementIndex){
		return $this->rows[$this->allElements[$elementIndex]['row']];
	}
	
	/*This stores the domain of the current page.*/
	function setDomain($domain){
		$this->_domain=$domain;
	}
	
	/*This returns the domain of the current page in memory.*/
	function getDomain(){
		return $this->_domain;
	}
	
	/*This is the second main function.  It takes all the stylesheets in the current page and extracts the rules and attributes.
	You need to pass an array containing the default media types ($default_media).  If you do not include a file location, it uses the current page location.  This function WILL slow things down a bit, so if you're not interested in the styles of this page, do not run it.*/
	function parse_stylesheet($default_media, $file_location='default', $merge_with_file=false, $cache_files=false){
		//here we need to use the get_css function to retrieve the data for each stylesheet
		//usage: get_css(stylesheet_text, default_media, $is_inline, $merge_with_file);
		
		if($file_location=='default')
			$file_location=$this->currentPage;
		
		//get all the css elements (style, link) in the correct order and then scan them
		$css_elements=Array();
		$applied_stylesheets=Array();
		
		//first check that there's no <merged_css> element on this html tree.
		//If there is, then use that.
		$merged=$this->getElementsByTagName('merged_css');
		if(sizeof($merged)>0){
			$styles=$this->innerHTML($merged[0]);
			
			//echo '<pre>';
			//print_r($styles);
			//echo '</pre>';
			//echo substr($styles, 1681638);
			if($this->getAttribute($merged[0], 'encode')=='base64')
				$base64=true;
			else
				$base64=false;
				
			$styles=__unserialize(trim($styles), $base64);
			
				
			if($styles){
				$this->_styles=$styles;
				//index these styles:
				if(is_array($this->_styles)){
					foreach($this->_styles as $key=>$this_stylesheet){
						//Empty stylesheets won't have any contents, so we need to check here:
						if(isset($this_stylesheet['styles'])){
							//quick check, is this an inline style?
							(@$this_stylesheet['selectors'][0]['is_inline']==true) ? $type='inline' : $type='normal';
						
							$this->index_styles($this_stylesheet['styles'], $this_stylesheet['selectors'], $key, $type);
						}
					}
					return true;
				}
			}
			//If we got this far, then the unserialization failed or there was nothing
			//there.  So we shall carry on as though there were no styles attached...
		}
		$temp=$this->getElementsByTagName('link');
		
		foreach($temp as $this_link)
			$css_elements[$this_link]='link';
				
		$temp=$this->getElementsByTagName('style');
		foreach($temp as $this_style)
			$css_elements[$this_style]='style';	
		
		foreach($css_elements as $key=>$this_css_type){
			if($this_css_type=='link'){
				$type=$this->getAttribute($key, 'type', true);
				$rel=$this->getAttribute($key, 'rel', true);
				if($type=='text/css' || $rel=='stylesheet' || $rel=='alternate stylesheet'){
					//ok so this appears to be a stylesheet <link> element, now get the href
					$href=$this->href($key);
					if($href!=''){
						//retrieve the stylesheet
						$file_ok=false;
						$headers=fetch_file($href, $cache_files);
						
						if(is_response_good($headers, 'text/css')){
							$file_ok=true;
							//get the media specification for this stylesheet and set up the media array with this info
							if($this->specified($this_link, 'media')){
								$media_bits=explode(',', $this->getAttribute($key, 'media', true));
								$media=Array();
								
								if(count($media_bits))
									foreach($media_bits as $this_media)
										$media[]=trim($this_media);
								else
									$media=$default_media;
									
							} else
								$media=$default_media;
							
							$contents=$headers['contents'];
						}
						
						if($file_ok){
							$applied_stylesheets=get_css($this->_styles, $contents, $media, false, '', $href, $cache_files, $this->_imported_stylesheets);
							foreach($applied_stylesheets as $this_applied_stylesheet)
								$applied_stylesheets[]=$this_applied_stylesheet;
						}
					}
				}
			}
			
			if($this_css_type=='style'){
				$type=$this->getAttribute($key, 'type', true);
				if($type=='text/css' || $type==''){
					//get the media specification for this stylesheet and set up the media array with this info
					if($this->specified($key, 'media')){
						$media_val=$this->getAttribute($key, 'media', true);
						
						if($media_val!=''){
							$media_bits=explode(',', $media_val);
							$media=Array();
							foreach($media_bits as $this_media)
								$media[]=trim($this_media);
						} else
							$media=$default_media;
						
					} else 
						$media=$default_media;
						
					//parse the stylesheet
					$contents=$this->innerHTML($key);
					$applied_stylesheets=get_css($this->_styles, $contents, $media, false, '', $this->currentPage, $cache_files, $this->_imported_stylesheets);
					
					foreach($applied_stylesheets as $this_applied_stylesheet)
						$applied_stylesheets[]=$this_applied_stylesheet;
				}
			}
		}
						
		//ok, now get any inline styles that may be present
		foreach($this->_attribute_lookup['style'] as $this_element)				
			get_css($this->_styles, $this->getAttribute($this_element, 'style'), Array(0=>'all'), true, $this_element, $this->currentPage, false, $this->_imported_stylesheets);
		
		//index these bad boys!
		foreach($this->_styles as $key=>$this_stylesheet){
			//Empty stylesheets won't have any contents, so we need to check here:
			if(isset($this_stylesheet['styles'])){
				//quick check, is this an inline style?
				(@$this_stylesheet['selectors'][0]['is_inline']==true) ? $type='inline' : $type='normal';
				
				$this->index_styles($this_stylesheet['styles'], $this_stylesheet['selectors'], $key, $type);
			}
		}
		
		if($merge_with_file){
			//right, here we are going to serialise the _styles array and attach it to the end of the file.
			//We are going to use a special element to contain this text
			//TODO: it would be really cool to put put this into a separate file and share it with other pages from the
			//same site so that file sizes can be reduced
			
			//Ok, we need to first check to see if this already exists.
			$merged=$this->getElementsByTagName('merged_css');
			if(sizeof($merged)!=0){
				$this->_full_string=substr($this->_full_string, 0, strpos('<merged_css', $this->_full_string));
			}
			
			$this->_full_string.="<merged_css encode=\"base64\" imported=\"true\">\n" . __serialize($this->_styles) . "\n</merged_css>";
			
		}
		//echo 'stylesheets: <pre>';
		//print_r($this->_styles);
		//echo '</pre>';
	}
	
	/*This returns an array of the complete list of CSS styles that directly apply to this element, but not inherited styles.  A media type must also be supplied, the default is 'screen'.*/
	function getStyles($elementIndex, $media='screen'){
		//so we have an element source index.
		//We need to see if there are any selectors containing this tagname, or class or index (if any)
		
		//This is the core style retrieval function.  It is probably not a good idea to directly query this, rather you should
		//use one of the intermediary functions such as getStyleValue
		$md5=md5(strtolower($this->fullElement($elementIndex)));
			
		//Is this element index already present in the _styles_lookup array?
		$instance_found=false;
		$hash_found=false;

		if(isset($this->_styles_lookup[$media][$elementIndex]) || isset($this->_styles_hash_lookup[$media][$md5])){
			
			if(isset($this->_styles_hash_lookup[$media][$md5]))
				$hash_found=true;
			
			$instance_found=true;
		}

		$original_elementIndex=$elementIndex;
		
		if(!$instance_found){
			$results=Array();
			
			//we get a more complete list of element attributes further on down...
			$element_tagname=$this->tagname($elementIndex);
			
			//Ok so we have the relevant key details (sans attributes and parents), are there any selectors matching these?
			//We have a really neat element lookup table which will make this a snap.
			//We need to include * element selectors as well
			$element_selectors=Array();
			
			if(isset($this->_selector_lookup[$element_tagname]))
				$element_selectors=array_merge($element_selectors, $this->_selector_lookup[$element_tagname]);
			
			if(isset($this->_selector_lookup['*']))
				$element_selectors=array_merge($element_selectors, $this->_selector_lookup['*']);
			 
			//okay, time to examine the actual selectors in question:
			foreach($element_selectors as $this_selector){
				//This $selector array holds all the information about what is required to use this rule
				//TODO: Currently media information is only held in the ['styles'] equivilant - maybe this should be moved to the 'selectors' listing?
				$elementIndex=$original_elementIndex;
				//$selector_groups=$this->styles[$this_selector['stylesheet']]['selectors'][$this_selector['rule']][$this_selector['selector_group']];
				$style=$this_selector;
				$selector_groups=$this->_styles[$this_selector['stylesheet']]['selectors'][$this_selector['rule']];			

				if($this_selector['type']=='normal'){
					$all_media=$this->_styles[$this_selector['stylesheet']]['styles'][$this_selector['rule']][0]['media'];

					//each selector within this group will have the same media setting, so we can just use the first one...
					//we need to check to see if the media attributes match what we want.
					$media_ok=false;
					
					foreach($all_media as $this_media){
						//We have an array of required media ($media), and if any of the $all_media entries
						//match any of the required media, then this selector is ok.
						if($this_media==$media || $this_media=='all'){
							$media_ok=true;
							break;
						}	
					}
					
					if($media_ok){
						foreach($selector_groups as $selector_group_key=>$this_selector_group){
							//ok, go through each selector and check to see if there is an element tree which matches this
							$style_found=true;
							
							foreach($this_selector_group as $key => $this_selector_outer){
								//NOTE: if any more exceptions are added to the selector group entry, then we may
								//need to redesign the layout...
								//if($selector_group_key!=='specificity'){
								if(is_numeric($key)){
									
									//We need to move up the element tree to find an element
									//of the sort we're looking for
									$found=false;
									$requirements=$this_selector_outer;
									
									while(!$found){
										if($elementIndex==-1)
											break;
										$element_tagname=$this->tagname($elementIndex);
										$element_id=$this->getAttribute($elementIndex, 'id', true);
										$element_class=$this->className($elementIndex, true);
										//we aren't going to get the child/parent details here.... yet
										if($this_selector_outer['element_selector']==$element_tagname || $this_selector_outer['element_selector']=='*')
											unset($requirements['element_selector']);
										
										if(isset($this_selector_outer['id']) && $this_selector_outer['id']==$element_id)
											unset($requirements['id']);
																				
										if(isset($this_selector_outer['class'])){
											//ok, this might be more than one class; so we need to check that they're both present in the element
											$all_classes=$this_selector_outer['class'];
											$all_classes_clone=$all_classes;
											if(is_array($element_class)){
												foreach($all_classes as $class_key=>$this_class){

													if(isset($element_class[$this_class]))
														unset($all_classes_clone[$class_key]);
													
												}
											}

											if(count($all_classes_clone)==0)
												unset($requirements['class']);
										
										}
											
										if(isset($this_selector_outer['parent_selector'])){
											//ok, we need to check the parent for a few details
											$parent_elementIndex=$this->parentElement($elementIndex);
											$parent_tagname=$this->tagname($parent_elementIndex);
											$parent_id=$this->getAttribute($parent_elementIndex, 'id', true);
											$parent_class=$this->className($parent_elementIndex, true);
										
											if($this_selector_outer['parent_selector']==$parent_tagname || $this_selector_outer['parent_selector']=='*')
												unset($requirements['parent_selector']);
												
											if(isset($this_selector_outer['parent_id']) && $this_selector_outer['parent_id']==$parent_id)
												unset($requirements['parent_id']);
												
											if(isset($this_selector_outer['parent_class'])){
												//ok, this might be more than one class; so we need to check that they're both present in the element
												$all_classes=$this_selector_outer['parent_class'];
												$all_classes_clone=$all_classes;
												if(is_array($parent_class)){
													//reset($all_classes);
													foreach($all_classes as $class_key => $this_class){
														
														if(isset($parent_class[$this_class]))
															unset($all_classes_clone[$class_key]);
														
													}
												}
												
												if(count($all_classes_clone)==0)
													unset($requirements['parent_class']);
											
											}
										}
										
										if(isset($this_selector_outer['adjacent_selector'])){
											//ok, this element and the next element of the specified type both have to have the
											//same parent element
											
											//first things first, lets get the next element
											$adjacent_elementIndex=$this->getNextElement($elementIndex, 'backward');
											
											$adjacent_tagname=$this->tagname($adjacent_elementIndex);
											$adjacent_id=$this->getAttribute($adjacent_elementIndex, 'id', true);
											$adjacent_class=$this->className($adjacent_elementIndex, true);
											
											//is this the correct element?
											if($adjacent_tagname==$this_selector_outer['adjacent_selector']){
												//ok, now check that the parent element is the same as the original selector:
												$adjacent_parentIndex=$this->parentElement($adjacent_elementIndex);
												$element_parentIndex=$this->parentElement($elementIndex);
												unset($requirements['adjacent_selector']);
												//ok, so they both have same parent, let's look at the remaining requirements
												if(isset($this_selector_outer['adjacent_id']) && $this_selector_outer['adjacent_id']==$adjacent_id)
													unset($requirements['adjacent_id']);
												
												if(isset($this_selector_outer['adjacent_class']) && $this_selector_outer['adjacent_class']==$adjacent_class)
													unset($requirements['adjacent_class']);
													
												if(isset($this_selector_outer['adjacent_class'])){
													//ok, this might be more than one class; so we need to check that they're both present in the element
													$all_classes=$this_selector_outer['adjacent_class'];
													$all_classes_clone=$all_classes;
													if(is_array($adjacent_class)){
														//reset($all_classes);
														foreach($all_classes as $class_key => $this_class){
															if(isset($adjacent_class[$this_class]))
																unset($all_classes_clone[$class_key]);
															
															//next($all_classes);
														}
													}
													if(count($all_classes_clone)==0)
														unset($requirements['adjacent_class']);
												
												}
												
											} else
												break;
										}
										
										if(isset($this_selector_outer['attribute_selector'])){
											//there are many types of attribute selectors. We shall analyse them based on the presence
											//of an '=' character
											$bits=explode('=', $this_selector_outer['attribute_selector']);
											if(count($bits)==1){
												//this is a simple attribute selector
												//Find out if this element has this attribute
												if($this->specified($elementIndex, $bits[0])==true)
													unset($requirements['attribute_selector']);
													
											} else {
												$attribute=str_replace(Array('^', '$', '*'), '', $bits[0]);
												if($this->specified($elementIndex, $attribute)==true){
													$char=substr($bits[0], -1);
													$required_value=str_replace(Array("'", "\""), '', $bits[1]);
													$value=$this->getAttribute($elementIndex, $attribute, true);
													switch($char){
														//TODO: implement the remaining attribute selectors.  Find out exactly
														//what they are supposed to do...
														case '^':
															//the attribute must start with this value
															if(substr($value, 0, strlen($required_value))==$required_value)
																unset($requirements['attribute_selector']);
																
															break;
															
														case '$':
															//the attribute must end with this value:
															if(substr($value, -(strlen($required_value)))==$required_value)
																unset($requirements['attribute_selector']);
															
															break;
															
														case '*':
															//Must appear somewhere as a substring
															if(strpos($value, $required_value)!==false)
																unset($requirements['attribute_selector']);
																
															break;
															
														default:
															//assumes that this is an attribute value selector
															//todo: implement the others to make this the actual default
															if($char!='~' && $char!='|'){
																//this element must have the attribute matching this value
																if($value==$required_value)
																	unset($requirements['attribute_selector']);
															}
													}
												}
											}
											
										}
										
										if(isset($this_selector_outer['pseudoClass'])){
											switch($this_selector_outer['pseudoClass']){
												case 'link':
													//make sure that this element is actually an anchor:
													if($this->tagname($elementIndex)=='a')
														unset($requirements['pseudoClass']);
														
													break;
													
												//we are ignoring :visited, :hover, :active and :focus styles since they are interactive
												//and we are (currently) only dealing with static elements
											}
										}
										
										if(count($requirements)==0){
											$found=true;
											break;
										}
															
										if($elementIndex==-1)
											break;
										
										//We only do a tree check if there are more than one selectors in this group
										if(sizeof($this_selector_group)<=2)
											break;
										
										//move up the tree...
										$elementIndex=$this->parentElement($elementIndex);
									}
	
									if(!$found){
										//If this is not true, then the entire element tree has been checked and no element structure
										//matches this selector group.  Therefore we can abandon the rest of this loop.
										$style_found=false;
										break;
									}
								} else {
									if(!$found)
										$style_found=false; //We don't want the specificity details being entered in as a style!
								}
							}
							
							if($style_found){
								//ok, this style is fine, add it to the list
								$results_count=sizeof($results);
								$results[$results_count]['stylesheet']=$style['stylesheet'];
								$results[$results_count]['rule']=$style['rule'];
								$results[$results_count]['selector_group']=$style['selector_group'];
								$results[$results_count]['inline']=false;
							}
						}
					}
				} else {
					//this is an inline style...
					//Inline styles only apply if the element index matches this element in question...
					if($selector_groups['element_index']==$elementIndex){		
						$results_count=sizeof($results);
						$results[$results_count]['stylesheet']=$style['stylesheet'];
						$results[$results_count]['rule']=$style['rule'];
						//$results[$results_count]['selector_group']=$style['selector_group'];
						$results[$results_count]['inline']=true;
					}
				}
				
			}
		
			//we need to store this result in each instance of the media types we are searching for:
			$this->_styles_lookup[$media][$original_elementIndex]=$results;
			$this->_styles_hash_lookup[$media][$md5]=$original_elementIndex;
							
			return $results;
		} else {
			
			if($hash_found)
				return $this->_styles_lookup[$media][$this->_styles_hash_lookup[$media][$md5]];
			else return $this->_styles_lookup[$media][$elementIndex];
		}
	}
	
	/*This returns the value of a particular style for an element, given a media type.
	This will only return the actual applied value, not all of the styles that are relevant to this element.*/
	function getStyleValue($elementIndex, $property, $media='screen'){
		//This will only return the actual applied value, not all of the styles that are relevant to this element
		//Inline styles override everything, unless there is an !important declaration.  !important inline styles override absolutely everything
		$results=Array();
	
		//Is this element index already present in the _styles_lookup array?
		if(!isset($this->_styles_value_lookup[$media][$elementIndex][$property])){
			$property=strtolower($property);
			$applied_list=$this->getStyles($elementIndex, $media);
			$requires_important=false;
			$is_inline=false;
			foreach($applied_list as $this_applied){
				$properties=$this->_styles[$this_applied['stylesheet']]['styles'][$this_applied['rule']];
				$selector=$this->_styles[$this_applied['stylesheet']]['selectors'][$this_applied['rule']];

				foreach($properties as $key => $this_property){
					if(is_numeric($key)){
						if($this_property['property']==$property){
							//$style=$this->styles[$this_applied['stylesheet']]['styles'][$this_applied['rule']][$this_applied['selector_group']];
							if(!$requires_important || $this_property['important']==true){
								//If it's not requiring an !important selector, or it it does and this rule has an !important selector, then
								//we shall use this one...
								//If this is an inline style, we can only check it if the elementIndex is the same...
								if($properties['inline']==false || ($properties['inline']==true && $properties['selector']==$elementIndex)){
									//ok, do a check on the denary information in the specificity value
									$rule_good=true;
									if($properties['inline']==false){
										foreach($selector as $this_selector){
											$specificity=$this_selector['specificity'];
											//Here things get a bit tricky.
											//If the new 'a' value is higher or equal than the old, then we use this
											//The same goes for 'b' and 'c'.
											
											if($specificity['a']<@$results['specificity']['a'])
												$rule_good=false;
											else if ($specificity['b']<@$results['specificity']['b'])
												$rule_good=false;
											else if ($specificity['c']<@$results['specificity']['c'])
												$rule_good=false;
										
										}
									}
									
									if($rule_good){
										$results['selector']=$this_property['selector'];
										$results['property']=$this_property['property'];
										$results['value']=$this_property['value'];
										$results['important']=$this_property['important'];
										$results['media']=$this_property['media'];
										
										$results['rule_text']=$properties['rule_text'];
										$results['inline']=$properties['inline'];
										$results['specificity']=@$specificity;
										
										//If this is an !important selector, then only other !importants will be used...
										if($this_property['important']==true)
											$requires_important=true;
											
										//If this is an inline style, then only importants can override this.
										if($properties['inline']==true){
											//If this is an inline !important, then nothing can replace it.  We can exit the look here.
											if($requires_important==true)
												break;
												
											$is_inline=true;
											$requires_important=true;
										}
									}
								}
							}
						}
					}
				}
			}
			
			//we need to store this result in each instance of the media types we are searching for:
			$this->_styles_value_lookup[$media][$elementIndex][$property]=$results;
			
		} else
			$results=$this->_styles_value_lookup[$media][$elementIndex][$property];
		
		if(sizeof($results)==0)
			return false;
		else return $results;
		
		//Keep this line for debugging purposes:
		//$style=$this->styles[$this_selector['stylesheet']]['styles'][$this_selector['rule']][$this_selector['selector_group']];
		
	}
	
	/*This returns a list of styles that contain a particular property for the supplied media type. 
	If a particular type of selector needs to be excluded, specify it in the $ignore_selectors array.
	Pseudo classes can be filtered out by $ignore_pseudo=true (the default value).
	This does not relate it to HTML elements.*/
	function getStylesByProperty($property, $media='screen', $ignore_selectors=Array(), $ignore_pseudo=true){
		
		//return any styles which contain a certain property
		//We can use the property lookup table which will make this pretty easy :)
		
		//TODO: this broadly ignores all pseudo classes, do we want to filter the list a bit more closely?
		
		$property=strtolower($property);
		
		$results=Array();
		
		if(isset($this->_property_lookup[$property])){
			$all_styles=$this->_property_lookup[$property];
			foreach($all_styles as $this_style){
				$properties=$this->_styles[$this_style['stylesheet']]['styles'][$this_style['rule']];
				$selector=$this->_styles[$this_style['stylesheet']]['selectors'][$this_style['rule']];
					
				foreach($properties as $this_property => $val){
					//check the media of this property:
					$media_ok=false;
					if(is_numeric($this_property)){
						
						foreach($val['media'] as $this_media){
							if($this_media==$media || $this_media=='all'){
								$media_ok=true;
								break;
							}	
						}
						
						if($val['property']==$property && $media_ok){
							//two last checks:
							//check each group...
							//get each selector...if all of them have psuedo classes, we will ignore this rule
							$ignore=false;
							if($ignore_pseudo){
								//ignore inline selectors here...
								if(!@$selector['is_inline']){
									foreach($selector as $this_group){
										$has_pseudo=false;
										$ignore_selector=false;
										
										foreach($this_group as $key => $this_selector){
											if(is_numeric($key)){
												if(isset($this_selector['pseudoClass']))
													$has_pseudo=true;
												
												if(in_array($this_selector['element_selector'], $ignore_selectors))
													$ignore_selector=true;
													
											}
											
										}
										
										if($has_pseudo || $ignore_selector){
											$ignore=true;
											//we want to exit as soon as we find an instance of a pseudo selector
											break;
										} else {
											$ignore=false;
											break;
										}
										
									}
								}
							}
							
							//Sometimes we want to ignore particular selectors, for instance 'a' selectors
							//when used for detection of underlines.
							if(!$ignore){
								$count=sizeof($results);
								$results[$count]=$val;
								$results[$count]['rule_text']=$properties['rule_text'];
								$results[$count]['inline']=$properties['inline'];
								$results[$count]['stylesheet']=$this_style['stylesheet'];
								$results[$count]['rule']=$this_style['rule'];
								$results[$count]['selector_group']=$this_style['selector_group'];
								$results[$count]['location']=$this->_styles[$this_style['stylesheet']]['location'];
							}
						}
					}
										
				}
				
			}	
		}
		
		return $results;
	}
	
	function calculateFontSize($elementIndex){
		//This function is called by the getComputedStyle function.  It doesn't return any result, it just updates the
		//attribute array with the font size information for every element below what you're interested in.
		$list=Array();
		$use_default=false;
		while($elementIndex>=0){
			//$calc_font_size=$this->getAttribute($elementIndex, 'calculated-font-size');
			$calc_font_size=@$this->allElements[$elementIndex]['attributes']['calculated-font-size']['value'];
			if($calc_font_size==''){
				//none set, add this element to the list and move up the tree
				$list[]=$elementIndex;
				
				if($elementIndex==-1){
					$use_default=true;
					break;
				}
				$elementIndex=$this->parentElement($elementIndex);
			} else break;
			
		}
		
		//Here we are going to set the calculated-font-size value to be 16 pixels on the very first element (last in the list)
		if($use_default)
			$this->allElements[$list[sizeof($list)-1]]['attributes']['calculated-font-size']['value']='16';
		
		//right, now we can go back up the tree calculating the font size for each of these.
		$list=array_reverse($list);
		$current_font_size='';
		foreach($list as $this_element){
			
			if(isset($this->allElements[$this_element]['attributes']['calculated-font-size']['value']))
				$current_font_size=$this->allElements[$this_element]['attributes']['calculated-font-size']['value'];
			
			/*
			1em=16px
			//1ex=7.207207px
			1ex=7px
			100%=800px (maybe 1024?)
			bigger=1.5em
			smaller=0.5em
			*/
			$fontsize=$this->getStyleValue($this_element, 'font-size');
			
			if(is_array($fontsize)){
				$fontsize_value=$fontsize['value'];
				$fontsize_unit=get_measurement_unit($fontsize_value);
				if($fontsize_unit=='em')
					$current_font_size=$current_font_size * substr($fontsize_value,0, -2);
				
				elseif($fontsize_unit=='ex')
					$current_font_size=($current_font_size * substr($fontsize_value,0, -2))*0.4375;
				
				elseif($fontsize_unit=='%')
					$current_font_size=$current_font_size * (substr($fontsize_value, 0, -1)/100);
					
				elseif($fontsize_unit=='bigger')
					$current_font_size=$current_font_size * 1.5;
				
				elseif($fontsize_unit=='smaller')
					$current_font_size=$current_font_size * 0.5;
					
				elseif($fontsize_unit=='xx-small')
					$current_font_size=9;
				
				elseif($fontsize_unit=='x-small')
					$current_font_size=10;
					
				elseif($fontsize_unit=='small')
					$current_font_size=13;
				
				elseif($fontsize_unit=='medium')
					$current_font_size=16;
				
				elseif($fontsize_unit=='large')
					$current_font_size=18;
					
				elseif($fontsize_unit=='x-large')
					$current_font_size=24;
					
				elseif($fontsize_unit=='xx-large')
					$current_font_size=32;
					
				$this->allElements[$this_element]['attributes']['calculated-font-size']['value']=$current_font_size;
			} else {
				//no font size specified for this element, so we shall use the $current_font_size value
				$this->allElements[$this_element]['attributes']['calculated-font-size']['value']=$current_font_size;
			}
		}
	}
	/*This returns the value of an attribute for an element, given it's parent elements and any applied styles.  Note that the value may not be actually specified by this element, but parent elements' values may be inherited (ie, background colours).
	$css_only will restrict the checks to just HTML attributes, while $media is the default media value if none is supplied.*/
	function getComputedStyle($elementIndex, $property, $css_only=false, $media='screen', $convert_relative_units=false){
		
		/*
		Thoughts:
		We need to go up the entire tree getting all the values that have been set for this property.  We can stop at the first absolute unit.
		In the case where no absolute value has ever been set, we need to use a default for the unit (ie, 1em=16px)
		Then, from the first absolute unit, we can calculate the current absolute value for this property
		When we hit a relative unit, we can take the previous value and multiply it by the relative multiplier or % if it's a percentage
		
		//How to get the defaults:
		//We need to go up the tree to find the first instance of font-size being set, and then calculate the current font-size to the
		//point of the element in question (ie, the 
		
		So what we need to do is if we are required to convert relative units, is to launch a 
		call to a special function to calculate the font sizes up to the first non-relative unit.
		We can store this value in a special html attribute of each element
		
		For example:
		We have got our element which has an absolute measurement.
		
		Suggestions:
		1em=16px
		1ex=7px
		100%=800px (maybe 1024?)
		bigger=1.5em
		smaller=0.5em
		*/
				
		//Todo: shouldn't the $media value be an array?
		
		//Move up the tree until we find an element with this setting.
		//When (if) we do, archive this result for the original element, so
		//that in future we do not have to explore the entire tree for every element
		
		//TODO:  I don't think that converting relative units is working too well with HTML attributes.
		//		What do the font sizes 1-7 actually map to?
		$found_html=false;
		$list=Array();
		$results=Array();
		$found_relative=false;
		
		while($elementIndex>-1){
			
			if(!isset($this->_computed_styles_lookup[$elementIndex][$property])){
				$details=$this->getStyleValue($elementIndex, $property, $media);
				if($details!=false && $details['value']!='transparent' && $details['value']!='inherit' && $details['value']!='none'){
					//ok, this apparently has a value.
					//If we only want the first value, then we can exit here, otherwise put it into the list
					if(!$convert_relative_units){
						$details['type']='css';
						$results=$details;
						$this->_computed_styles_lookup[$elementIndex][$property]=$results;
						break;
					} else {
						$details['elementIndex']=$elementIndex;
						$details['type']='css';
						$list[]=$details;
						$results=$details;
						//we are going to stop searching if this is an absolute measurement
						if(!is_unit_relative($details['value'])){
							$found_relative=true;
							$this->_computed_styles_lookup[$elementIndex][$property]=$results;
							break;
						}
					}
				}
				//go through each of the possible HTML attributes and see if it's been set for this element.
				//Only do this if no style has been set so far
				if(!$css_only){
					if(!$found_html){
						$html_attriubtes=get_attribute_equivilent($property);
						foreach($html_attriubtes as $this_attribute){
							if($this_attribute['type']=='attribute'){
								//this is an attribute check, make sure that this element tagname is in the list
								if($this->specified($elementIndex, $this_attribute['value'])){
									if($this_attribute['tags'][0]=='*' || in_array($this->tagname($elementIndex), $this_attribute['tags'])){
										if(!$convert_relative_units){
											$results['value']=$this->getAttribute($elementIndex, $this_attribute['value']);
											$results['type']='html';
											$found_html=true;
											$this->_computed_styles_lookup[$elementIndex][$property]=$results;
											break;
										} else {
											$details['elementIndex']=$elementIndex;
											$details['value']=$this->getAttribute($elementIndex, $this_attribute['value']);
											$details['type']='html';
											$list[]=$details;
											$results=$details;
											//we are going to stop searching if this is an absolute measurement
											if(!is_unit_relative($details['value'])){
												$found_relative=true;
												$this->_computed_styles_lookup[$elementIndex][$property]=$results;
												break;
											}
										}
									}
								}
							} else {
								//This is a tagname check, make sure it's in the list
								if($this_attribute['tags'][0]=='*' || in_array($this->tagname($elementIndex), $this_attribute['tags'])){
									if(!$convert_relative_units){
										$results['value']=$this->getAttribute($elementIndex, $this_attribute['value']);
										$results['type']='html';
										$this->_computed_styles_lookup[$elementIndex][$property]=$results;
										$found_html=true;
										break;
									} else {
										//$results['value']=$this->getAttribute($elementIndex, $this_attribute['value']);
										$details['elementIndex']=$elementIndex;
										$details['value']=$this->getAttribute($elementIndex, $this_attribute['value']);
										$details['type']='html';
										$list[]=$details;
										$results=$details;
										//we are going to stop searching if this is an absolute measurement
										if(!is_unit_relative($details['value'], true)){
											$found_relative=true;
											$this->_computed_styles_lookup[$elementIndex][$property]=$results;
											break;
										}
									}
								}
							}
						}
					}
				}
				
				$elementIndex=$this->parentElement($elementIndex);
			} else {
				if(!$convert_relative_units){
					$results=$this->_computed_styles_lookup[$elementIndex][$property];
					break;
				} else {
					//$details['elementIndex']=$elementIndex;
					$details=$this->_computed_styles_lookup[$elementIndex][$property];
					$list[]=$details;
					$results=$details;
					//we are going to stop searching if this is an absolute measurement
					if(isset($details['value'])){
						if(!is_unit_relative(@$details['value'])){
							$found_relative=true;
							//$this->_computed_styles_lookup[$elementIndex][$property]=$results;
							break;
						}
					} else {
						//Here we are going to treat non-existant values as being relative
						$found_relative=true;
						//$this->_computed_styles_lookup[$elementIndex][$property]=$results;
						//break;
					}
				}
				
				$elementIndex=$this->parentElement($elementIndex);
			}
		}
	
		if($convert_relative_units){
			//ok, so apparently we now have a list of values, we need to convert them all,
			//starting with the topmost (ie, the element in question) element
			//The general rule here is that we move down the list to find the first non-relative unit
			//This will be the inherited value.
			
			//get the font sizes for this tree:
			if(isset($list[0]['elementIndex'])){
				$this->calculateFontSize($list[0]['elementIndex']);
				
				//If we did not find an absolute unit, then we need to specify the default value
				//Not for fonts though, because calculateFontSize will already have done that
				if(!$found_relative){
					//This needs to be a list of any property that *could* have a relative unit
					//TODO: how are we going to handle position words like 'center'?
					//TODO: complete this list:
					switch($property){
						case 'width': $list[]['value']=DEFAULT_SCREEN_WIDTH; break;
						case 'height': $list[]['value']='16px'; break; //Same as height of default font
						case 'margin-top': $list[]['value']='0px'; break;
						case 'margin-right': $list[]['value']='0px'; break;
						case 'margin-bottom': $list[]['value']='0px'; break;
						case 'margin-left': $list[]['value']='0px'; break;
						case 'margin': $list[]['value']='0px'; break;
						case 'padding-top': $list[]['value']='0px'; break;
						case 'padding-right': $list[]['value']='0px'; break;
						case 'padding-bottom': $list[]['value']='0px'; break;
						case 'padding-left': $list[]['value']='0px'; break;
						case 'padding': $list[]['value']='0px'; break;
						case 'line-height': $list[]['value']='16px'; break; //Font height
						case 'top': $list[]['value']='0px'; break; //TODO: this is probably going to cause confusion
						case 'right': $list[]['value']='0px'; break; //TODO: this is probably going to cause confusion
						case 'bottom': $list[]['value']='0px'; break; //TODO: this is probably going to cause confusion
						case 'left': $list[]['value']='0px'; break; //TODO: this is probably going to cause confusion
						case 'border-top': $list[]['value']='3px'; break;
						case 'border-right': $list[]['value']='3px'; break;
						case 'border-bottom': $list[]['value']='3px'; break;
						case 'border-left': $list[]['value']='3px'; break;
						case 'border-width': $list[]['value']='3px'; break;		
					}
				}
				
				//ok, so now go back from this point, converting all the relative units based on this initial 
				$list=array_reverse($list);
				//echo 'list:<pre>';
				//print_r($list);
				//echo '</pre>';
				//The very first entry will be the absolute unit we found, so we shall store this for use by percentage measurements for instance
				$current_value=$list[0]['value'];
				$unit=get_measurement_unit($current_value);
				if($unit=='')
					$value=$list[0]['value'];
				else
					$value=substr($current_value, 0, -strlen($unit));
				for($i=1; $i<sizeof($list); $i++){
					//get the unit of this element and do stuff based on what it is
					$temp_unit=get_measurement_unit($list[$i]['value']);
					$fontsize=$this->allElements[$list[$i]['elementIndex']]['attributes']['calculated-font-size']['value'];
					if($temp_unit=='em')
						$value=$fontsize * substr($list[$i]['value'], 0, -2);
					
					elseif($temp_unit=='ex')
						$value=($fontsize * substr($list[$i]['value'], 0, -2))*0.4375;
					
					elseif($temp_unit=='%')
						$value=$value * (substr($list[$i]['value'], 0, -1)/100);
						
					elseif($temp_unit=='bigger')
						$value=$fontsize * 1.5;
					
					elseif($temp_unit=='smaller')
						$value=$fontsize * 0.5;
						
					elseif($temp_unit=='xx-small')
						$value=9;
					
					elseif($temp_unit=='x-small')
						$value=10;
						
					elseif($temp_unit=='small')
						$value=13;
					
					elseif($temp_unit=='medium')
						$value=16;
					
					elseif($temp_unit=='large')
						$value=18;
						
					elseif($temp_unit=='x-large')
						$value=24;
						
					elseif($temp_unit=='xx-large')
						$value=32;
					else
						$value=$list[$i]['value'];
							
					$list[$i]['value']=$value . 'px';
				}
				
				$results=$list[sizeof($list)-1];
			}
		}
		
		//echo 'returning: <pre>';
		//print_r($results);
		//echo '</pre>';
		return $results;
	}
	
	/*function getElementByPos($row='', $column='', $get_source_indices=true){
		//given a row and column number, return the source index OR element index
		
		if($row!='' && $column!=''){
			//go through each element to this row:
			$all_elements=$this->allElements;
			$found_line=false;
			$source_index=-1;
			$last=-1;
			
			foreach($all_elements as $this_element){
				if(isset($this_element['line'])){
					if($this_element['line']==$row){
						$found_line=true;
					}
					if($this_element['line']!=$row && $found_line){
						$source_index=$last;
						break;
					}
				}
				if($found_line){
					if($this_element['column']==$column){
						if($get_source_indices){
							$source_index=$this_element['sourceIndex'];
						} else {
							$source_index=$this_element['elementIndex'];
						}
						break;
					}
					if($this_element['column']<$column){
						if($get_source_indices){
							$last=$this_element['sourceIndex'];
						} else {
							$last=$this_element['elementIndex'];
						}
					}
					if($this_element['column']>$column){
						$source_index=$last;
						break;
					}
				}
			}
			
			return $source_index;
		} else {
			return -1;
		}
	}
	*/
	function index_styles($_styles, $selectors, $stylesheet_index, $type){
		/*
		STYLES:
		[0] => Array
        (
            [0] => Array
                (
                    [selector] => @import
                    [property] => @import
                    [url] => import3.css
                    [media] => Array
                        (
                            [0] => all
                        )

                )

            [selector] => @import
        )
        $_styles[$stylesheet_count]['styles'][$selector_index][$count]
        the $styles variable begins at [$selector_index][$count]
        
		*/
		foreach($_styles as $rule_index_key => $rule_index){
			if(is_numeric($rule_index_key)){
				foreach($rule_index as $this_group_key => $this_group){
					if(is_numeric($this_group_key)){
						$property=$this_group['property'];
						$count=sizeof(@$this->_property_lookup[$property]);
						
						$this->_property_lookup[$property][$count]['stylesheet']=$stylesheet_index;
						$this->_property_lookup[$property][$count]['rule']=$rule_index_key;
						$this->_property_lookup[$property][$count]['selector_group']=$this_group_key;
						$this->_property_lookup[$property][$count]['type']=$type;
						
					}
				}
			}
				
		}
		
		//Now create an element selector lookup table:
		/*
		SELECTORS:
		[0] => Array
        (
            [body] => Array
                (
                    [0] => Array
                        (
                         	[element_selector] => body

                        )

                    [specificity] => Array
                        (
                            [a] => 0
                            [b] => 0
                            [c] => 1
                        )

                )

        )
		$_styles[$stylesheet_count]['selectors'][$selector_index][$this_selector_group][$selector_count]
		*/
		if($type=='normal'){
			//This is NOT an inline style
			
			foreach($selectors as $selector_index_key => $selector_index){
				foreach($selector_index as $group_index_key => $group_index){
					foreach($group_index as $style_index_key => $style_index){
						
						if(is_numeric($style_index_key)){
							//TODO: move the element selector and associated information up one in the tree, get rid of the $key dimension
							$element_selector=$style_index['element_selector'];
							$count=sizeof(@$this->_selector_lookup[$element_selector]);
							$this->_selector_lookup[$element_selector][$count]['stylesheet']=$stylesheet_index;
							$this->_selector_lookup[$element_selector][$count]['rule']=$selector_index_key;
							$this->_selector_lookup[$element_selector][$count]['selector_group']=$group_index_key;
							$this->_selector_lookup[$element_selector][$count]['type']=$type;
						}
					}
				}
			}
			
		} else {
			//Inline styles have a different structure (ie, no selector)
			//The question is how do we identify inline selectors ?
			//We shall make it a redefined element, but with a type of 'inline';
			
			/*
			[selectors] => Array
                (
                    [0] => Array
                        (
                            [is_inline] => 1
                            [element_index] => 11
                        )

                )
               */
			foreach($selectors as $selector_index_key => $selector_index){
				$tagname=$this->tagname($selector_index['element_index']);
				$count=sizeof(@$this->_selector_lookup[$tagname]);
				$this->_selector_lookup[$tagname][$count]['stylesheet']=$stylesheet_index;
				$this->_selector_lookup[$tagname][$count]['rule']=$selector_index_key;
				$this->_selector_lookup[$tagname][$count]['type']=$type;
			}
		}
		
	}
	
	/*This returns the next elementIndex in the array, in either a 'forward' or 'backward' $direction.  By default it ignores closing elements, but this can be changed in $include_closing_tags*/
	function getNextElement($elementIndex, $direction='forward', $include_closing_tags=false){
		
		$count=sizeof($this->allElements)-1;
		
		if($elementIndex>=$count)
			return false;
		
		while($elementIndex<$count){
			($direction=='forward') ? $elementIndex++ : $elementIndex--;
			
			if($elementIndex==-1)
				break;
			
			if(!$include_closing_tags){
				if($this->allElements[$elementIndex]['is_closing']==false)
					return $elementIndex;
				
			} else
				return $elementIndex;
			
		}
		
		//if we got here, then nothing was found...
		return false;
	}
	
	/*This returns the physical location of the provided stylesheet index ($stylesheet).*/
	function getStyleSheetLocation($stylesheet){
		return @$this->_styles[$stylesheet]['location'];
	}

	/*This identifies if this element is an orphan or not.  This usually applies to closing elements that haven't been nested correctly.*/
	function isOrphan($elementIndex){
		
		if(isset($this->allElements[$elementIndex]['orphan']))
			return $this->allElements[$elementIndex]['orphan'];
		else return false;
		
	}
	
	/*Returns true if $child_element is a child of $parent_element, otherwise false*/
	function isChild($child_element, $parent_element){
		$is_child=false;
		$element_index=$child_element;
		while($element_index>=$parent_element){
			if($element_index==0)
				break;
				
			if($element_index==$parent_element){
				$is_child=true;
				break;
			}
			$element_index=$this->parentElement($element_index);
		}
		
		return $is_child;
	}
	
	function isParent($parent_element, $child_element){
		
		$children=$this->all_descendants($parent_element);
		foreach($children as $this_child){
			if($this_child==$child_element)
				return true;
		}
		
		return false;
	}
	
	/* goes through the HTML tree to find out if an element ($parent_tag_name) is a parent of the current element */
	function has_parent($elementIndex, $parent_tags){
		//Note: $parent_tags should be an Array
		
		$currentElement=$elementIndex;
		$found=false;
		do {
			
			if(in_array($this->allElements[$currentElement]['tagname'], $parent_tags)){
				$found=true;
				break;
			}
			
			$currentElement=$this->allElements[$currentElement]['parentElement'];
						
		} while ($currentElement>0);
		
		return $found;
	}
	
	function getContentElements(){
		return $this->_content_elements;
	}
	
/*	function importCSS($css){
		$this->_cached_stylesheets=$css;
	}
	
	function exportCSS(){
		return $this->_cached_stylesheets;
	}*/
	
}

?>
Return current item: OpenWolf Guidelines Validator