3 * Logiciel : HTML2PDF - classe ParsingHTML
5 * Convertisseur HTML => PDF
6 * Distribué sous la licence LGPL.
8 * @author Laurent MINGUET <webmaster@html2pdf.fr>
14 var $html = ''; // code HTML à parser
15 var $num = 0; // numéro de table
16 var $level = 0; // niveaux de table
17 var $encoding = ''; // encodage
18 var $code = array(); // code HTML parsé
25 function parsingHTML($encoding = 'ISO-8859-15')
28 $this->level
= array($this->num
);
30 $this->code
= array();
31 $this->setEncoding($encoding);
34 function setEncoding($encoding)
36 $this->encoding
= $encoding;
40 * Définir le code HTML à parser
42 * @param string code html
45 function setHTML($html)
47 $html = preg_replace('/<!--(.*)-->/isU', '', $html);
60 // chercher les balises HTML du code
62 $this->searchCode($tmp);
64 // identifier les balises une à une
75 $balises_no_closed = array(
76 'br', 'hr', 'img', 'col',
77 'input', 'link', 'option',
78 'circle', 'ellipse', 'path', 'rect', 'line', 'polygon', 'polyline'
81 foreach($tmp as $part)
86 $res = $this->analiseCode($part[1]);
88 // si le code est bien un code analisable
91 $res['html_pos'] = $part[2];
92 if (!in_array($res['name'], $balises_no_closed))
96 if (count($parents)<1)
97 HTML2PDF
::makeError(3, __FILE__
, __LINE__
, $res['name'], $this->getHtmlErrorCode($res['html_pos']));
98 else if ($parents[count($parents)-1]!=$res['name'])
99 HTML2PDF
::makeError(4, __FILE__
, __LINE__
, $parents, $this->getHtmlErrorCode($res['html_pos']));
101 unset($parents[count($parents)-1]);
105 if ($res['autoclose'])
108 $res['params'] = array();
109 $res['close'] = true;
112 $parents[count($parents)] = $res['name'];
115 if (($res['name']=='pre' ||
$res['name']=='code') && !$res['autoclose'])
116 $pre_in = !$res['close'];
121 // sinon (code non analisable) => on le transforme en texte
127 // sinon si c'est un texte
130 // enregistrer l'action correspondante
133 // remplacer tous les espaces, tabulations, saufs de ligne multiples par de simples espaces
137 'param' => array('txt' => $this->prepareTxt($part[1])),
142 $part[1] = str_replace("\r", '', $part[1]);
143 $part[1] = explode("\n", $part[1]);
145 foreach($part[1] as $k => $txt)
147 $txt = str_replace("\t", ' ', $txt);
148 $txt = str_replace(' ', ' ', $txt);
149 if ($k>0) $todos[] = $pre_br;
154 'param' => array('txt' => $this->prepareTxt($txt, false)),
161 // pour chaque action identifiée, il faut nettoyer le début et la fin des textes
162 // en fonction des balises qui l'entourent.
163 $balises_clean = array('page', 'page_header', 'page_footer', 'form',
164 'table', 'thead', 'tfoot', 'tr', 'td', 'th', 'br',
165 'div', 'hr', 'p', 'ul', 'ol', 'li',
166 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
167 'bookmark', 'fieldset', 'legend',
168 'draw', 'circle', 'ellipse', 'path', 'rect', 'line', 'g', 'polygon', 'polyline');
170 for($k=0; $k<$nb; $k++
)
173 if ($todos[$k]['name']=='write')
175 // et qu'une balise spécifique le précède => on nettoye les espaces du début du texte
176 if ($k>0 && in_array($todos[$k-1]['name'], $balises_clean))
177 $todos[$k]['param']['txt'] = ltrim($todos[$k]['param']['txt']);
179 // et qu'une balise spécifique le suit => on nettoye les espaces de la fin du texte
180 if ($k<$nb-1 && in_array($todos[$k+
1]['name'], $balises_clean))
181 $todos[$k]['param']['txt'] = rtrim($todos[$k]['param']['txt']);
183 if (!strlen($todos[$k]['param']['txt']))
187 if (count($parents)) HTML2PDF
::makeError(5, __FILE__
, __LINE__
, $parents);
189 // liste des actions sauvée
190 $this->code
= array_values($todos);
194 * preparer le texte une seule fois pour gagner du temps. vient de o_WRITE
196 * @param string texte
197 * @return string texte
199 function prepareTxt($txt, $spaces = true)
201 if ($spaces) $txt = preg_replace('/\s+/is', ' ', $txt);
202 $txt = str_replace('€', '€', $txt);
203 $txt = html_entity_decode($txt, ENT_QUOTES
, $this->encoding
);
208 * parser le code HTML
210 * @param &array tableau de retour des données
213 function searchCode(&$tmp)
215 // séparer les balises du texte
217 $reg = '/(<[^>]+>)|([^<]+)+/isU';
219 // pour chaque élément trouvé :
222 while(preg_match($reg, $this->html
, $parse, PREG_OFFSET_CAPTURE
, $offset))
224 // si une balise a été détectée
227 // sauvegarde du texte précédent si il existe
228 if ($str!=='') $tmp[] = array('txt',$str);
230 // sauvegarde de la balise
231 $tmp[] = array('code',trim($parse[1][0]), $offset);
233 // initialisation du texte suivant
238 // ajout du texte à la fin de celui qui est déjà détecté
241 // Update offset to the end of the match
242 $offset = $parse[0][1] +
strlen($parse[0][0]);
245 // si un texte est présent à la fin, on l'enregistre
246 if ($str!='') $tmp[] = array('txt',$str);
251 * analyse une balise HTML
253 * @param string code HTML à identifier
254 * @return array action correspondante
256 function analiseCode($code)
258 // nom de la balise et ouverture ou fermeture
259 $balise = '<([\/]{0,1})([_a-z0-9]+)([\/>\s]+)';
260 if (!preg_match('/'.$balise.'/isU', $code, $match))
263 $close = ($match[1]=='/' ?
true : false);
264 $autoclose = preg_match('/\/>$/isU', $code);
265 $name = strtolower($match[2]);
267 // paramètres obligatoires en fonction du nom de la balise
269 $param['style'] = '';
270 if ($name=='img') { $param['alt'] = ''; $param['src'] = ''; }
271 if ($name=='a') { $param['href'] = ''; }
273 // lecture des paramétres du type nom=valeur
274 $prop = '([a-zA-Z0-9_]+)=([^"\'\s>]+)';
275 preg_match_all('/'.$prop.'/is', $code, $match);
276 for($k=0; $k<count($match[0]); $k++
)
277 $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
279 // lecture des paramétres du type nom="valeur"
280 $prop = '([a-zA-Z0-9_]+)=["]([^"]*)["]';
281 preg_match_all('/'.$prop.'/is', $code, $match);
282 for($k=0; $k<count($match[0]); $k++
)
283 $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
285 // lecture des paramétres du type nom='valeur'
286 $prop = "([a-zA-Z0-9_]+)=[']([^']*)[']";
287 preg_match_all('/'.$prop.'/is', $code, $match);
288 for($k=0; $k<count($match[0]); $k++
)
289 $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
291 // mise en conformité en style de chaque paramètre
294 foreach($param as $key => $val)
296 $key = strtolower($key);
301 $param['style'] .= 'width: '.$val.'px; ';
308 $param['style'] .= 'float: '.$val.'; ';
310 elseif ($name!=='table')
313 $param['style'] .= 'text-align: '.$val.'; ';
319 $param['style'] .= 'vertical-align: '.$val.'; ';
324 $param['style'] .= 'height: '.$val.'px; ';
329 $param['style'] .= 'background: '.$val.'; ';
339 if (preg_match('/^[0-9]+$/isU', $val)) $val = $val.'px';
345 if (preg_match('/^([0-9]+)$/isU', $val)) $param[$key] = $val.'px';
350 $val = preg_replace('/[^0-9]/isU', '', $val);
358 if ($border) $border = 'border: solid '.$border.' '.$color;
359 else $border = 'border: none';
361 $param['style'] .= $border.'; ';
362 $param['border'] = $border;
365 // lecture des styles - décomposition
366 $styles = explode(';', $param['style']);
367 $param['style'] = array();
368 foreach($styles as $style)
370 $tmp = explode(':', $style);
373 $cod = $tmp[0]; unset($tmp[0]); $tmp = implode(':', $tmp);
374 $param['style'][trim(strtolower($cod))] = preg_replace('/[\s]+/isU', ' ', trim($tmp));
378 // détermination du niveau de table pour les ouverture, avec ajout d'un level
379 if (in_array($name, array('ul', 'ol', 'table')) && !$close)
382 $this->level
[count($this->level
)] = $this->num
;
385 // attribution du niveau de table où se trouve l'élément
386 if (!isset($param['num'])) $param['num'] = $this->level
[count($this->level
)-1];
388 // pour les fins de table : suppression d'un level
389 if (in_array($name, array('ul', 'ol', 'table')) && $close)
391 unset($this->level
[count($this->level
)-1]);
394 if (isset($param['value'])) $param['value'] = $this->prepareTxt($param['value']);
395 if (isset($param['alt'])) $param['alt'] = $this->prepareTxt($param['alt']);
396 if (isset($param['title'])) $param['title'] = $this->prepareTxt($param['title']);
397 if (isset($param['class'])) $param['class'] = $this->prepareTxt($param['class']);
399 // retour de l'action identifiée
400 return array('name' => $name, 'close' => $close ?
1 : 0, 'autoclose' => $autoclose, 'param' => $param);
403 // récupérer un niveau complet d'HTML entre une ouverture de balise et la fermeture correspondante
404 function getLevel($k)
406 // si le code n'existe pas : fin
407 if (!isset($this->code
[$k])) return array();
409 // quelle balise faudra-t-il détecter
410 $detect = $this->code
[$k]['name'];
412 $level = 0; // niveau de profondeur
413 $end = false; // etat de fin de recherche
414 $code = array(); // code extrait
416 // tant que c'est pas fini, on boucle
420 $row = $this->code
[$k];
422 // si write => on ajoute le texte
423 if ($row['name']=='write')
427 // sinon, c'est une balise html
430 $not = false; // indicateur de non prise en compte de la balise courante
432 // si c'est la balise que l'on cherche
433 if ($row['name']==$detect)
435 if ($level==0) { $not = true; } // si on est à la premiere balise : on l'ignore
436 $level+
= ($row['close'] ?
-1 : 1); // modification du niveau en cours en fonction de l'ouvertre / fermeture
437 if ($level==0) { $not = true; $end = true; } // si on est au niveau 0 : on a fini
440 // si on doit prendre en compte la balise courante
443 if (isset($row['style']['text-align'])) unset($row['style']['text-align']);
448 // on continue tant qu'il y a du code à analyser...
449 if (isset($this->code
[$k+
1]))
455 // retourne le code extrait
459 function getHtmlErrorCode($pos)
461 return substr($this->html
, $pos-30, 70);