<?php
/**
* Parse a ui template file to ui dom
* document
*
* @package frea-framework
* @subpackage Template
*
* @copyright 2009 frea-framework
* @author Dawid Kraczkowski hide@address.com
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
*/
class Template_Dom_Parser
{
const UTF8 = "utf-8";
const BOM = "\xef\xbb\xbf";
/**
* states
*/
const ST_DOCUMENT = 0;
const ST_DOCTYPE = 1;
const ST_XMLDEC = 2;
const ST_TAGNAME = 3;
const ST_CLOSETAG = 10;
const ST_TAG = 4;
const ST_ATTRNAME = 5;
const ST_ATTREQ = 6;
const ST_ATTRVAL = 7;
const ST_ATTRQUOTE = 9;
const ST_TEXT = 8;
static public $states = array(
self::ST_DOCUMENT => "Start of document",
self::ST_TEXT => "Html content",
self::ST_TAG => "Tag content",
self::ST_TAGNAME => "tagname",
self::ST_ATTRNAME => "attribute name",
self::ST_ATTREQ => "attribute value declaration",
self::ST_ATTRQUOTE => "attribute quote",
self::ST_ATTRVAL => "attribute value",
self::ST_DOCTYPE => "doctype declaration",
self::ST_XMLDEC => "xml declaration",
self::ST_CLOSETAG => "closing tag",
);
public $cLine,$cChar;
private $dom;
private $rawstr;
public $fileName;
/**
* constructor
*
* @param string $filename
*/
public function __construct($filename)
{
$this->rawstr = file_get_contents($filename);
$this->fileName = $filename;
}
/**
* parse the string
*
* @throws Template_Dom_Parser_Exception
* @return Template_Dom_Node_Document
*/
public function parse()
{
$str = $this->rawstr;
//initial values
$i = 0;
$length = strlen($str);
if(substr($str,0,3)==self::BOM) $i = 3;
$this->cLine = 1;
$state = self::ST_DOCUMENT ;
$prevstate = self::ST_DOCUMENT ;
$doctype = "";
$content = "";
$tagname = "";
$attributename = "";
$attributevalue = "";
$attributequote = '';
$htmlcontents = "";
$closetag = "";
$starttagLine = 0;
$this->dom = new Template_Dom_Node_Document($this->fileName);
$builder = $this->dom;
for (; $i<$length; $i++)
{
$this->cChar++;
$c = $str[$i];
if($c === "\n")
{
$this->cLine++;//current line increment
$this->cChar = 1;//reset current char
}
//
$prevstate = $state;
switch($state)
{
case self::ST_DOCUMENT :
/* removed
if($c === '<' & substr($str,$i,9)=="<!DOCTYPE")
{
$state = self::ST_DOCTYPE ;
$builder = $builder->appendChild(new Template_Dom_Node_Doctype());
$i += 8;
}*/
if($c === '<' & substr($str,$i,4) == "<ui:")
{
$state = self::ST_TAGNAME;
$starttagLine = $this->cLine;
$tagname = "";
$i += 3;
}
else
{
$state = self::ST_TEXT ;
$htmlcontents .= $c;
}
break;
case self::ST_DOCTYPE :
if($c === '>')
{
$builder->value($doctype);
$builder = $builder->parentNode();
$doctype = "";
$state = self::ST_DOCUMENT ;
}
else
{
$doctype .= $c;
}
break;
case self::ST_TEXT :
if($c === '<' && substr($str,$i,4)=="<ui:")
{
if(strlen($htmlcontents))
{
$builder = $builder->appendChild(new Template_Dom_Node_Text());
$builder->value($htmlcontents);
$builder = $builder->parentNode();
}
$htmlcontents = "";
$state = self::ST_TAGNAME;
$tagname = "";
$i += 3;
}
elseif($c === '<' && substr($str,$i,5) == "</ui:")
{
$i += 4;
$htmlcontents = trim($htmlcontents);
if(strlen($htmlcontents))
{
$builder = $builder->appendChild(new Template_Dom_Node_Text());
$builder->value($htmlcontents);
$builder = $builder->parentNode();
}
$htmlcontents = "";
$state = self::ST_CLOSETAG ;
}
else
{
$htmlcontents .= $c;
//last char for full text document
if($i+1 == $length)
{
$builder = $builder->appendChild(new Template_Dom_Node_Text());
$builder->value($htmlcontents);
}
}
break;
case self::ST_CLOSETAG :
if($c === '>')
{
if($builder->nodeName() != trim($closetag))
throw new Template_Dom_Parser_Exception("Missing closing tag {$builder->nodeName()} given {$closetag} in file {$this->fileName} on line {$this->cLine}");
$builder->onClose($this,$builder,$starttagLine,$this->cLine);
$builder = $builder->parentNode();
$closetag = "";
$state = self::ST_TEXT ;
}
else
{
$closetag .= $c;
}
break;
case self::ST_TAGNAME :
if(self::isWhiteChar($c))
{
if(!self::checkNames($tagname))
throw new Template_Dom_Parser_Exception("Wrong tag name in file {$this->fileName} on line {$this->cLine}, given {$tagname}");
$builder = $builder->appendChild(new Template_Dom_Node_Element($tagname));
$tagname = "";
$starttagLine = $this->cLine;
$state = self::ST_TAG ;
}
elseif($c === '>')
{
if(!self::checkNames($tagname))
throw new Template_Dom_Parser_Exception("Wrong tag name in file {$this->fileName} on line {$this->cLine}");
$builder = $builder->appendChild(new Template_Dom_Node_Element($tagname));
$tagname = "";
$state = self::ST_TEXT ;
}
else
{
$tagname .= $c;
}
break;
case self::ST_TAG :
if($c === '/' and substr($str,$i,2) == "/>")
{
$builder->onClose($this,$builder,$starttagLine,$this->cLine);
$builder = $builder->parentNode();
$i += 1;
$state = self::ST_TEXT ;
}
elseif($c === '>')
{
$state = self::ST_TEXT ;
}
elseif(!self::isWhiteChar($c))
{
$attributename .= $c;
$state = self::ST_ATTRNAME ;
}
break;
case self::ST_ATTRNAME :
if($c === '=')
{
$attributename = trim($attributename);
if(!self::checkNames($attributename))
throw new Template_Dom_Parser_Exception("Wrong attribute name in file {$this->fileName} on line {$this->cLine}, given: {$attributename}");
$state = self::ST_ATTREQ ;
}
else
{
$attributename .= $c;
}
break;
case self::ST_ATTREQ :
if($c === '\'' || $c === '"')
{
$attributequote = $c;
$state = self::ST_ATTRVAL ;
}
elseif(!self::isWhiteChar($c))
{
throw new Template_Dom_Parser_Exception("Unexpeted '{$c}' sign in file {$this->fileName} on line {$this->cLine}");
}
break;
case self::ST_ATTRVAL :
if($c === $attributequote)
{
$builder->setAttribute($attributename,$attributevalue);
$attributequote = '';
$attributename = '';
$attributevalue = '';
$state = self::ST_TAG ;
}
else
{
$attributevalue .= $c;
}
break;
}
}
return $this->dom;
}
/**
* check if givnt tag/attribute name is valid
*
* @param string $str
* @return bool
*/
static public function checkNames($str)
{
return preg_match("#^[a-z][a-z0-9\\\-]#is",$str);
}
/**
* check if given char is white char
*
* @param char $c
* @return bool
*/
static public function isWhiteChar($c)
{
return strpos(" \t\n\r\0", $c) !== false;
}
}