123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440 |
- <?php
- /**
- * Logiciel : HTML2PDF - classe ParsingHTML
- *
- * Convertisseur HTML => PDF, utilise fpdf de Olivier PLATHEY
- * Distribué sous la licence LGPL.
- *
- * @author Laurent MINGUET <webmaster@spipu.net>
- * @version 3.26 - 16/11/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();
- // chercher les balises HTML du code
- $tmp = array();
- $this->searchCode($tmp);
-
- // identifier les balises une à une
- $pre_in = false;
- $pre_br = array(
- 'name' => 'br',
- 'close' => false,
- 'param' => array(
- 'style' => array(),
- 'num' => 0
- )
- );
- $balises_no_closed = array('br', 'hr', 'img', 'input', 'link', 'option', 'col');
- $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)
- {
- $res['html_pos'] = $part[2];
- if (!in_array($res['name'], $balises_no_closed))
- {
- if ($res['close'])
- {
- if (count($parents)<1)
- @HTML2PDF::makeError(3, __FILE__, __LINE__, $res['name'], $this->getHtmlErrorCode($res['html_pos']));
- else if ($parents[count($parents)-1]!=$res['name'])
- @HTML2PDF::makeError(4, __FILE__, __LINE__, $parents, $this->getHtmlErrorCode($res['html_pos']));
- else
- unset($parents[count($parents)-1]);
- }
- else
- {
- if ($res['autoclose'])
- {
- $todos[] = $res;
- $res['params'] = array();
- $res['close'] = true;
- }
- else
- $parents[count($parents)] = $res['name'];
-
- }
- if (($res['name']=='pre' || $res['name']=='code') && !$res['autoclose'])
- $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'entourent.
- $balises_clean = array('page', 'page_header', 'page_footer', 'form',
- 'table', 'thead', 'tfoot', 'tr', 'td', 'th', 'br',
- 'div', 'hr', 'p', 'ul', 'ol', 'li',
- 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
- 'bookmark');
- $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'], $balises_clean))
- $todos[$k]['param']['txt'] = ltrim($todos[$k]['param']['txt']);
- // et qu'une balise spécifique le suit => on nettoye les espaces de la fin du texte
- if ($k<count($todos)-1 && in_array($todos[$k+1]['name'], $balises_clean))
- $todos[$k]['param']['txt'] = rtrim($todos[$k]['param']['txt']);
-
- if (!strlen($todos[$k]['param']['txt']))
- unset($todos[$k]);
- }
- }
- if (count($parents)) @HTML2PDF::makeError(5, __FILE__, __LINE__, $parents);
- // liste des actions sauvée
- $this->code = array_values($todos);;
- }
-
- /**
- * parser le code HTML
- *
- * @param &array tableau de retour des données
- * @return null
- */
- function searchCode(&$tmp)
- {
- // séparer les balises du texte
- $tmp = array();
- $reg = '/(<[^>]+>)|([^<]+)+/isU';
- // pour chaque élément trouvé :
- $str = '';
- $offset = 0;
- while(preg_match($reg, $this->html, $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]), $offset);
-
- // 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);
- $autoclose = preg_match('/\/>$/isU', $code);
- $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<count($match[0]); $k++)
- $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
- // lecture des paramétres du type nom="valeur"
- $prop = '([a-zA-Z0-9_]+)=["]([^"]*)["]';
- preg_match_all('/'.$prop.'/is', $code, $match);
- for($k=0; $k<count($match[0]); $k++)
- $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
- // lecture des paramétres du type nom='valeur'
- $prop = "([a-zA-Z0-9_]+)=[']([^']*)[']";
- preg_match_all('/'.$prop.'/is', $code, $match);
- for($k=0; $k<count($match[0]); $k++)
- $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
-
- // mise en conformité en style de chaque paramètre
- $color = "#000000";
- $border = null;
- foreach($param as $key => $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'.$param['style'];
- }
-
- // 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, 'autoclose' => $autoclose, '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);
- }
-
- function getHtmlErrorCode($pos)
- {
- return substr($this->html, $pos-30, 70);
- }
- }
- }
|