PDF, utilise fpdf de Olivier PLATHEY
* Distribué sous la licence GPL.
*
* @author Laurent MINGUET
* @version 3.21 - 05/05/2009
*/
if (!defined('__CLASS_PARSINGHTML__'))
{
define('__CLASS_PARSINGHTML__', true);
class parsingHTML
{
var $html = ''; // code HTML à parser
var $code = array(); // code HTML parsé
var $num = 0; // numéro de table
var $level = 0; // niveaux de table
/**
* Constructeur
*
* @return null
*/
function parsingHTML()
{
$this->num = 0;
$this->level = array($this->num);
$this->html = '';
$this->code = array();
}
/**
* Définir le code HTML à parser
*
* @param string code html
* @return null
*/
function setHTML($html)
{
$html = preg_replace('//isU', '', $html);
$this->html = $html;
}
/**
* parser le code HTML
*
* @return null
*/
function parse()
{
$parents = array();
// récupérer le code à parser
$content = $this->html;
// chercher les balises HTML du code
$tmp = array();
$this->searchCode($content, $tmp);
// identifier les balises une à une
$pre_in = false;
$pre_br = array(
'name' => 'br',
'close' => false,
'param' => array(
'style' => array(),
'num' => 0
)
);
$todos = array();
foreach($tmp as $part)
{
// si c'est un texte
if ($part[0]=='txt')
{
// enregistrer l'action correspondante
if (!$pre_in)
{
if (trim($part[1])!=='')
{
// remplacer tous les espaces, tabulations, saufs de ligne multiples par de simples espaces
$part[1] = preg_replace('/([\s]+)/is', ' ', $part[1]);
$todos[] = array(
'name' => 'write',
'close' => false,
'param' => array('txt' => $part[1]),
);
}
}
else
{
$part[1] = str_replace("\r", '', $part[1]);
$part[1] = explode("\n", $part[1]);
foreach($part[1] as $k => $txt)
{
$txt = str_replace("\t", ' ', $txt);
$txt = str_replace(' ', ' ', $txt);
if ($k>0) $todos[] = $pre_br;
$todos[] = array(
'name' => 'write',
'close' => false,
'param' => array('txt' => $txt),
);
}
}
}
// sinon, analyser le code
else
{
$res = $this->analiseCode($part[1]);
if ($res)
{
if (!in_array($res['name'], array('br', 'hr', 'img', 'input', 'link', 'option')))
{
if ($res['close'])
{
if (count($parents)<1)
HTML2PDF::makeError(3, __FILE__, __LINE__, $res['name']);
else if ($parents[count($parents)-1]!=$res['name'])
HTML2PDF::makeError(4, __FILE__, __LINE__, $parents);
else
unset($parents[count($parents)-1]);
}
else
{
$parents[count($parents)] = $res['name'];
}
}
if ($res['name']=='pre' || $res['name']=='code')
{
$pre_in = !$res['close'];
}
$todos[] = $res;
}
}
}
// pour chaque action identifiée, il faut nettoyer le début et la fin des textes
// en fonction des balises qui l'antourent.
$nb = count($todos);
for($k=0; $k<$nb; $k++)
{
//si c'est un texte
if ($todos[$k]['name']=='write')
{
// et qu'une balise spécifique le précède => on nettoye les espaces du début du texte
if ($k>0 && in_array($todos[$k-1]['name'], array('table', 'tr', 'td', 'th', 'br', 'div', 'hr', 'p', 'ul', 'ol', 'li')))
$todos[$k]['param']['txt'] = preg_replace('/^([\s]*)([^\s])/isU', '$2', $todos[$k]['param']['txt']);
// et qu'une balise spécifique le suit => on nettoye les espaces de la fin du texte
if ($kcode = $todos;
}
/**
* parser le code HTML
*
* @param string contenu à parser.
* @param &array tableau de retour des données
* @return null
*/
function searchCode($content, &$tmp)
{
// séparer les balises du texte
$tmp = array();
$reg = '/(<[^>]+>)|([^<]+)+/isU';
// pour chaque élément trouvé :
$str = '';
$offset = 0;
while(preg_match($reg, $content, $parse, PREG_OFFSET_CAPTURE, $offset))
{
// si une balise a été détectée
if ($parse[1][0])
{
// sauvegarde du texte précédent si il existe
if ($str!=='') $tmp[] = array('txt',$str);
// sauvegarde de la balise
$tmp[] = array('code',trim($parse[1][0]));
// initialisation du texte suivant
$str = '';
}
else
{
// ajout du texte à la fin de celui qui est déjà détecté
$str.= $parse[2][0];
}
// Update offset to the end of the match
$offset = $parse[0][1] + strlen($parse[0][0]);
unset($parse);
}
// si un texte est présent à la fin, on l'enregistre
if ($str!='') $tmp[] = array('txt',$str);
unset($str);
}
/**
* analyse une balise HTML
*
* @param string code HTML à identifier
* @return array action correspondante
*/
function analiseCode($code)
{
// nom de la balise et ouverture ou fermeture
$balise = '<([\/]{0,1})([_a-z0-9]+)([\/>\s]+)';
preg_match('/'.$balise.'/isU', $code, $match);
$close = ($match[1]=='/' ? true : false);
$name = strtolower($match[2]);
// paramètres obligatoires en fonction du nom de la balise
$param = array();
$param['style'] = '';
if ($name=='img') { $param['alt'] = ''; $param['src'] = ''; }
if ($name=='a') { $param['href'] = ''; }
// lecture des paramétres du type nom=valeur
$prop = '([a-zA-Z0-9_]+)=([^"\'\s>]+)';
preg_match_all('/'.$prop.'/is', $code, $match);
for($k=0; $k $val)
{
$key = strtolower($key);
switch($key)
{
case 'width':
unset($param[$key]);
$param['style'] = 'width: '.$val.'px; '.$param['style'];
break;
case 'align':
if ($name!=='table')
{
unset($param[$key]);
$param['style'] = 'text-align: '.$val.'; '.$param['style'];
}
break;
case 'valign':
unset($param[$key]);
$param['style'] = 'vertical-align: '.$val.'; '.$param['style'];
break;
case 'height':
unset($param[$key]);
$param['style'] = 'height: '.$val.'px; '.$param['style'];
break;
case 'bgcolor':
unset($param[$key]);
$param['style'] = 'background: '.$val.'; '.$param['style'];
break;
case 'bordercolor':
unset($param[$key]);
$color = $val;
break;
case 'border':
unset($param[$key]);
if (preg_match('/^[0-9]$/isU', $val)) $val = $val.'px';
$border = $val;
break;
case 'cellpadding':
case 'cellspacing':
if (preg_match('/^([0-9]+)$/isU', $val)) $param[$key] = $val.'px';
break;
case 'colspan':
case 'rowspan':
$val = preg_replace('/[^0-9]/isU', '', $val);
if (!$val) $val = 1;
$param[$key] = $val;
break;
}
}
if ($border!==null)
{
if ($border) $param['style'] = 'border: solid '.$border.' '.$color.'; '.$param['style'];
else $param['style'] = 'border: none';
}
// lecture des styles - décomposition
$styles = explode(';', $param['style']);
$param['style'] = array();
foreach($styles as $style)
{
$tmp = explode(':', $style);
if (count($tmp)>1)
{
$cod = $tmp[0]; unset($tmp[0]); $tmp = implode(':', $tmp);
$param['style'][trim(strtolower($cod))] = preg_replace('/[\s]+/isU', ' ', trim($tmp));
}
}
// détermination du niveau de table pour les ouverture, avec ajout d'un level
if (in_array($name, array('ul', 'ol', 'table')) && !$close)
{
$this->num++;
$this->level[count($this->level)] = $this->num;
}
// attribution du niveau de table où se trouve l'élément
if (!isset($param['num'])) $param['num'] = $this->level[count($this->level)-1];
// pour les fins de table : suppression d'un level
if (in_array($name, array('ul', 'ol', 'table')) && $close)
{
unset($this->level[count($this->level)-1]);
}
// retour de l'action identifiée
return array('name' => $name, 'close' => $close ? 1 : 0, 'param' => $param);
}
// récupérer un niveau complet d'HTML entre une ouverture de balise et la fermeture correspondante
function getLevel($k)
{
// si le code n'existe pas : fin
if (!isset($this->code[$k])) return '';
// quelle balise faudra-t-il détecter
$detect = $this->code[$k]['name'];
$level = 0; // niveau de profondeur
$end = false; // etat de fin de recherche
$code = ''; // code extrait
// tant que c'est pas fini, on boucle
while (!$end)
{
// action courante
$row = $this->code[$k];
// si write => on ajoute le texte
if ($row['name']=='write')
{
$code.= $row['param']['txt'];
}
// sinon, c'est une balise html
else
{
$not = false; // indicateur de non prise en compte de la balise courante
// si c'est la balise que l'on cherche
if ($row['name']==$detect)
{
if ($level==0) { $not = true; } // si on est à la premiere balise : on l'ignore
$level+= ($row['close'] ? -1 : 1); // modification du niveau en cours en fonction de l'ouvertre / fermeture
if ($level==0) { $not = true; $end = true; } // si on est au niveau 0 : on a fini
}
// si on doit prendre en compte la balise courante
if (!$not)
{
// ecriture du code HTML de la balise
$code.= '<'.($row['close'] ? '/' : '').$row['name'];
foreach($row['param'] as $key => $val)
{
if ($key=='style')
{
$tmp = '';
if (isset($val['text-align'])) unset($val['text-align']);
foreach($val as $ks => $vs) $tmp.= $ks.':'.$vs.'; ';
if (trim($tmp)) $code.= ' '.$key.'="'.$tmp.'"';
}
else
{
$code.= ' '.$key.'="'.$val.'"';
}
}
$code.= '>';
}
}
// on continue tant qu'il y a du code à analyser...
if (isset($this->code[$k+1]))
$k++;
else
$end = true;
}
// retourne la position finale et le code HTML extrait
return array($k, $code);
}
}
}
?>