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); } } } ?>