1 | <?php
|
---|
2 | /**
|
---|
3 | * Logiciel : HTML2PDF - classe ParsingHTML
|
---|
4 | *
|
---|
5 | * Convertisseur HTML => PDF, utilise fpdf de Olivier PLATHEY
|
---|
6 | * Distribué sous la licence GPL.
|
---|
7 | *
|
---|
8 | * @author Laurent MINGUET <webmaster@spipu.net>
|
---|
9 | * @version 3.21 - 05/05/2009
|
---|
10 | */
|
---|
11 |
|
---|
12 | if (!defined('__CLASS_PARSINGHTML__'))
|
---|
13 | {
|
---|
14 | define('__CLASS_PARSINGHTML__', true);
|
---|
15 |
|
---|
16 | class parsingHTML
|
---|
17 | {
|
---|
18 | var $html = ''; // code HTML à parser
|
---|
19 | var $code = array(); // code HTML parsé
|
---|
20 | var $num = 0; // numéro de table
|
---|
21 | var $level = 0; // niveaux de table
|
---|
22 |
|
---|
23 | /**
|
---|
24 | * Constructeur
|
---|
25 | *
|
---|
26 | * @return null
|
---|
27 | */
|
---|
28 | function parsingHTML()
|
---|
29 | {
|
---|
30 | $this->num = 0;
|
---|
31 | $this->level = array($this->num);
|
---|
32 | $this->html = '';
|
---|
33 | $this->code = array();
|
---|
34 | }
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Définir le code HTML à parser
|
---|
38 | *
|
---|
39 | * @param string code html
|
---|
40 | * @return null
|
---|
41 | */
|
---|
42 | function setHTML($html)
|
---|
43 | {
|
---|
44 | $html = preg_replace('/<!--(.*)-->/isU', '', $html);
|
---|
45 | $this->html = $html;
|
---|
46 | }
|
---|
47 |
|
---|
48 | /**
|
---|
49 | * parser le code HTML
|
---|
50 | *
|
---|
51 | * @return null
|
---|
52 | */
|
---|
53 | function parse()
|
---|
54 | {
|
---|
55 | $parents = array();
|
---|
56 | // récupérer le code à parser
|
---|
57 | $content = $this->html;
|
---|
58 |
|
---|
59 | // chercher les balises HTML du code
|
---|
60 | $tmp = array();
|
---|
61 | $this->searchCode($content, $tmp);
|
---|
62 |
|
---|
63 | // identifier les balises une à une
|
---|
64 | $pre_in = false;
|
---|
65 | $pre_br = array(
|
---|
66 | 'name' => 'br',
|
---|
67 | 'close' => false,
|
---|
68 | 'param' => array(
|
---|
69 | 'style' => array(),
|
---|
70 | 'num' => 0
|
---|
71 | )
|
---|
72 | );
|
---|
73 |
|
---|
74 | $todos = array();
|
---|
75 | foreach($tmp as $part)
|
---|
76 | {
|
---|
77 | // si c'est un texte
|
---|
78 | if ($part[0]=='txt')
|
---|
79 | {
|
---|
80 | // enregistrer l'action correspondante
|
---|
81 | if (!$pre_in)
|
---|
82 | {
|
---|
83 | if (trim($part[1])!=='')
|
---|
84 | {
|
---|
85 | // remplacer tous les espaces, tabulations, saufs de ligne multiples par de simples espaces
|
---|
86 | $part[1] = preg_replace('/([\s]+)/is', ' ', $part[1]);
|
---|
87 |
|
---|
88 | $todos[] = array(
|
---|
89 | 'name' => 'write',
|
---|
90 | 'close' => false,
|
---|
91 | 'param' => array('txt' => $part[1]),
|
---|
92 | );
|
---|
93 | }
|
---|
94 | }
|
---|
95 | else
|
---|
96 | {
|
---|
97 | $part[1] = str_replace("\r", '', $part[1]);
|
---|
98 | $part[1] = explode("\n", $part[1]);
|
---|
99 |
|
---|
100 | foreach($part[1] as $k => $txt)
|
---|
101 | {
|
---|
102 | $txt = str_replace("\t", ' ', $txt);
|
---|
103 | $txt = str_replace(' ', ' ', $txt);
|
---|
104 | if ($k>0) $todos[] = $pre_br;
|
---|
105 |
|
---|
106 | $todos[] = array(
|
---|
107 | 'name' => 'write',
|
---|
108 | 'close' => false,
|
---|
109 | 'param' => array('txt' => $txt),
|
---|
110 | );
|
---|
111 | }
|
---|
112 | }
|
---|
113 | }
|
---|
114 | // sinon, analyser le code
|
---|
115 | else
|
---|
116 | {
|
---|
117 | $res = $this->analiseCode($part[1]);
|
---|
118 | if ($res)
|
---|
119 | {
|
---|
120 | if (!in_array($res['name'], array('br', 'hr', 'img', 'input', 'link', 'option')))
|
---|
121 | {
|
---|
122 | if ($res['close'])
|
---|
123 | {
|
---|
124 | if (count($parents)<1)
|
---|
125 | HTML2PDF::makeError(3, __FILE__, __LINE__, $res['name']);
|
---|
126 | else if ($parents[count($parents)-1]!=$res['name'])
|
---|
127 | HTML2PDF::makeError(4, __FILE__, __LINE__, $parents);
|
---|
128 | else
|
---|
129 | unset($parents[count($parents)-1]);
|
---|
130 | }
|
---|
131 | else
|
---|
132 | {
|
---|
133 | $parents[count($parents)] = $res['name'];
|
---|
134 | }
|
---|
135 | }
|
---|
136 | if ($res['name']=='pre' || $res['name']=='code')
|
---|
137 | {
|
---|
138 | $pre_in = !$res['close'];
|
---|
139 | }
|
---|
140 | $todos[] = $res;
|
---|
141 | }
|
---|
142 | }
|
---|
143 | }
|
---|
144 |
|
---|
145 | // pour chaque action identifiée, il faut nettoyer le début et la fin des textes
|
---|
146 | // en fonction des balises qui l'antourent.
|
---|
147 | $nb = count($todos);
|
---|
148 | for($k=0; $k<$nb; $k++)
|
---|
149 | {
|
---|
150 | //si c'est un texte
|
---|
151 | if ($todos[$k]['name']=='write')
|
---|
152 | {
|
---|
153 | // et qu'une balise spécifique le précède => on nettoye les espaces du début du texte
|
---|
154 | if ($k>0 && in_array($todos[$k-1]['name'], array('table', 'tr', 'td', 'th', 'br', 'div', 'hr', 'p', 'ul', 'ol', 'li')))
|
---|
155 | $todos[$k]['param']['txt'] = preg_replace('/^([\s]*)([^\s])/isU', '$2', $todos[$k]['param']['txt']);
|
---|
156 |
|
---|
157 | // et qu'une balise spécifique le suit => on nettoye les espaces de la fin du texte
|
---|
158 | if ($k<count($todos)-1 && in_array($todos[$k+1]['name'], array('table', 'tr', 'td', 'th', 'br', 'div', 'hr', 'p', 'ol', 'ul', 'li')))
|
---|
159 | $todos[$k]['param']['txt'] = preg_replace('/([^\s])([\s]*)$/isU', '$1', $todos[$k]['param']['txt']);
|
---|
160 | }
|
---|
161 | }
|
---|
162 | if (count($parents)) HTML2PDF::makeError(5, __FILE__, __LINE__, $parents);
|
---|
163 |
|
---|
164 | // liste des actions sauvée
|
---|
165 | $this->code = $todos;
|
---|
166 | }
|
---|
167 |
|
---|
168 | /**
|
---|
169 | * parser le code HTML
|
---|
170 | *
|
---|
171 | * @param string contenu à parser.
|
---|
172 | * @param &array tableau de retour des données
|
---|
173 | * @return null
|
---|
174 | */
|
---|
175 | function searchCode($content, &$tmp)
|
---|
176 | {
|
---|
177 | // séparer les balises du texte
|
---|
178 | $tmp = array();
|
---|
179 | $reg = '/(<[^>]+>)|([^<]+)+/isU';
|
---|
180 |
|
---|
181 | // pour chaque élément trouvé :
|
---|
182 | $str = '';
|
---|
183 | $offset = 0;
|
---|
184 | while(preg_match($reg, $content, $parse, PREG_OFFSET_CAPTURE, $offset))
|
---|
185 | {
|
---|
186 | // si une balise a été détectée
|
---|
187 | if ($parse[1][0])
|
---|
188 | {
|
---|
189 | // sauvegarde du texte précédent si il existe
|
---|
190 | if ($str!=='') $tmp[] = array('txt',$str);
|
---|
191 |
|
---|
192 | // sauvegarde de la balise
|
---|
193 | $tmp[] = array('code',trim($parse[1][0]));
|
---|
194 |
|
---|
195 | // initialisation du texte suivant
|
---|
196 | $str = '';
|
---|
197 | }
|
---|
198 | else
|
---|
199 | {
|
---|
200 | // ajout du texte à la fin de celui qui est déjà détecté
|
---|
201 | $str.= $parse[2][0];
|
---|
202 | }
|
---|
203 | // Update offset to the end of the match
|
---|
204 | $offset = $parse[0][1] + strlen($parse[0][0]);
|
---|
205 | unset($parse);
|
---|
206 | }
|
---|
207 | // si un texte est présent à la fin, on l'enregistre
|
---|
208 | if ($str!='') $tmp[] = array('txt',$str);
|
---|
209 | unset($str);
|
---|
210 | }
|
---|
211 |
|
---|
212 | /**
|
---|
213 | * analyse une balise HTML
|
---|
214 | *
|
---|
215 | * @param string code HTML à identifier
|
---|
216 | * @return array action correspondante
|
---|
217 | */
|
---|
218 | function analiseCode($code)
|
---|
219 | {
|
---|
220 | // nom de la balise et ouverture ou fermeture
|
---|
221 | $balise = '<([\/]{0,1})([_a-z0-9]+)([\/>\s]+)';
|
---|
222 | preg_match('/'.$balise.'/isU', $code, $match);
|
---|
223 | $close = ($match[1]=='/' ? true : false);
|
---|
224 | $name = strtolower($match[2]);
|
---|
225 |
|
---|
226 | // paramètres obligatoires en fonction du nom de la balise
|
---|
227 | $param = array();
|
---|
228 | $param['style'] = '';
|
---|
229 | if ($name=='img') { $param['alt'] = ''; $param['src'] = ''; }
|
---|
230 | if ($name=='a') { $param['href'] = ''; }
|
---|
231 |
|
---|
232 | // lecture des paramétres du type nom=valeur
|
---|
233 | $prop = '([a-zA-Z0-9_]+)=([^"\'\s>]+)';
|
---|
234 | preg_match_all('/'.$prop.'/is', $code, $match);
|
---|
235 | for($k=0; $k<count($match[0]); $k++)
|
---|
236 | $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
|
---|
237 |
|
---|
238 | // lecture des paramétres du type nom="valeur"
|
---|
239 | $prop = '([a-zA-Z0-9_]+)=["]([^"]*)["]';
|
---|
240 | preg_match_all('/'.$prop.'/is', $code, $match);
|
---|
241 | for($k=0; $k<count($match[0]); $k++)
|
---|
242 | $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
|
---|
243 |
|
---|
244 | // lecture des paramétres du type nom='valeur'
|
---|
245 | $prop = "([a-zA-Z0-9_]+)=[']([^']*)[']";
|
---|
246 | preg_match_all('/'.$prop.'/is', $code, $match);
|
---|
247 | for($k=0; $k<count($match[0]); $k++)
|
---|
248 | $param[trim(strtolower($match[1][$k]))] = trim($match[2][$k]);
|
---|
249 |
|
---|
250 | // mise en conformité en style de chaque paramètre
|
---|
251 | $color = "#000000";
|
---|
252 | $border = null;
|
---|
253 | foreach($param as $key => $val)
|
---|
254 | {
|
---|
255 | $key = strtolower($key);
|
---|
256 | switch($key)
|
---|
257 | {
|
---|
258 | case 'width':
|
---|
259 | unset($param[$key]);
|
---|
260 | $param['style'] = 'width: '.$val.'px; '.$param['style'];
|
---|
261 | break;
|
---|
262 |
|
---|
263 | case 'align':
|
---|
264 | if ($name!=='table')
|
---|
265 | {
|
---|
266 | unset($param[$key]);
|
---|
267 | $param['style'] = 'text-align: '.$val.'; '.$param['style'];
|
---|
268 | }
|
---|
269 | break;
|
---|
270 |
|
---|
271 | case 'valign':
|
---|
272 | unset($param[$key]);
|
---|
273 | $param['style'] = 'vertical-align: '.$val.'; '.$param['style'];
|
---|
274 | break;
|
---|
275 |
|
---|
276 | case 'height':
|
---|
277 | unset($param[$key]);
|
---|
278 | $param['style'] = 'height: '.$val.'px; '.$param['style'];
|
---|
279 | break;
|
---|
280 |
|
---|
281 | case 'bgcolor':
|
---|
282 | unset($param[$key]);
|
---|
283 | $param['style'] = 'background: '.$val.'; '.$param['style'];
|
---|
284 | break;
|
---|
285 |
|
---|
286 | case 'bordercolor':
|
---|
287 | unset($param[$key]);
|
---|
288 | $color = $val;
|
---|
289 | break;
|
---|
290 |
|
---|
291 | case 'border':
|
---|
292 | unset($param[$key]);
|
---|
293 | if (preg_match('/^[0-9]$/isU', $val)) $val = $val.'px';
|
---|
294 | $border = $val;
|
---|
295 | break;
|
---|
296 |
|
---|
297 | case 'cellpadding':
|
---|
298 | case 'cellspacing':
|
---|
299 | if (preg_match('/^([0-9]+)$/isU', $val)) $param[$key] = $val.'px';
|
---|
300 | break;
|
---|
301 |
|
---|
302 | case 'colspan':
|
---|
303 | case 'rowspan':
|
---|
304 | $val = preg_replace('/[^0-9]/isU', '', $val);
|
---|
305 | if (!$val) $val = 1;
|
---|
306 | $param[$key] = $val;
|
---|
307 | break;
|
---|
308 | }
|
---|
309 | }
|
---|
310 | if ($border!==null)
|
---|
311 | {
|
---|
312 | if ($border) $param['style'] = 'border: solid '.$border.' '.$color.'; '.$param['style'];
|
---|
313 | else $param['style'] = 'border: none';
|
---|
314 | }
|
---|
315 |
|
---|
316 | // lecture des styles - décomposition
|
---|
317 | $styles = explode(';', $param['style']);
|
---|
318 | $param['style'] = array();
|
---|
319 | foreach($styles as $style)
|
---|
320 | {
|
---|
321 | $tmp = explode(':', $style);
|
---|
322 | if (count($tmp)>1)
|
---|
323 | {
|
---|
324 | $cod = $tmp[0]; unset($tmp[0]); $tmp = implode(':', $tmp);
|
---|
325 | $param['style'][trim(strtolower($cod))] = preg_replace('/[\s]+/isU', ' ', trim($tmp));
|
---|
326 | }
|
---|
327 | }
|
---|
328 |
|
---|
329 | // détermination du niveau de table pour les ouverture, avec ajout d'un level
|
---|
330 | if (in_array($name, array('ul', 'ol', 'table')) && !$close)
|
---|
331 | {
|
---|
332 | $this->num++;
|
---|
333 | $this->level[count($this->level)] = $this->num;
|
---|
334 | }
|
---|
335 |
|
---|
336 | // attribution du niveau de table où se trouve l'élément
|
---|
337 | if (!isset($param['num'])) $param['num'] = $this->level[count($this->level)-1];
|
---|
338 |
|
---|
339 | // pour les fins de table : suppression d'un level
|
---|
340 | if (in_array($name, array('ul', 'ol', 'table')) && $close)
|
---|
341 | {
|
---|
342 | unset($this->level[count($this->level)-1]);
|
---|
343 | }
|
---|
344 |
|
---|
345 | // retour de l'action identifiée
|
---|
346 | return array('name' => $name, 'close' => $close ? 1 : 0, 'param' => $param);
|
---|
347 | }
|
---|
348 |
|
---|
349 | // récupérer un niveau complet d'HTML entre une ouverture de balise et la fermeture correspondante
|
---|
350 | function getLevel($k)
|
---|
351 | {
|
---|
352 | // si le code n'existe pas : fin
|
---|
353 | if (!isset($this->code[$k])) return '';
|
---|
354 |
|
---|
355 | // quelle balise faudra-t-il détecter
|
---|
356 | $detect = $this->code[$k]['name'];
|
---|
357 |
|
---|
358 | $level = 0; // niveau de profondeur
|
---|
359 | $end = false; // etat de fin de recherche
|
---|
360 | $code = ''; // code extrait
|
---|
361 |
|
---|
362 | // tant que c'est pas fini, on boucle
|
---|
363 | while (!$end)
|
---|
364 | {
|
---|
365 | // action courante
|
---|
366 | $row = $this->code[$k];
|
---|
367 |
|
---|
368 | // si write => on ajoute le texte
|
---|
369 | if ($row['name']=='write')
|
---|
370 | {
|
---|
371 | $code.= $row['param']['txt'];
|
---|
372 | }
|
---|
373 | // sinon, c'est une balise html
|
---|
374 | else
|
---|
375 | {
|
---|
376 | $not = false; // indicateur de non prise en compte de la balise courante
|
---|
377 |
|
---|
378 | // si c'est la balise que l'on cherche
|
---|
379 | if ($row['name']==$detect)
|
---|
380 | {
|
---|
381 | if ($level==0) { $not = true; } // si on est à la premiere balise : on l'ignore
|
---|
382 | $level+= ($row['close'] ? -1 : 1); // modification du niveau en cours en fonction de l'ouvertre / fermeture
|
---|
383 | if ($level==0) { $not = true; $end = true; } // si on est au niveau 0 : on a fini
|
---|
384 | }
|
---|
385 |
|
---|
386 | // si on doit prendre en compte la balise courante
|
---|
387 | if (!$not)
|
---|
388 | {
|
---|
389 | // ecriture du code HTML de la balise
|
---|
390 | $code.= '<'.($row['close'] ? '/' : '').$row['name'];
|
---|
391 | foreach($row['param'] as $key => $val)
|
---|
392 | {
|
---|
393 | if ($key=='style')
|
---|
394 | {
|
---|
395 | $tmp = '';
|
---|
396 | if (isset($val['text-align'])) unset($val['text-align']);
|
---|
397 | foreach($val as $ks => $vs) $tmp.= $ks.':'.$vs.'; ';
|
---|
398 | if (trim($tmp)) $code.= ' '.$key.'="'.$tmp.'"';
|
---|
399 | }
|
---|
400 | else
|
---|
401 | {
|
---|
402 | $code.= ' '.$key.'="'.$val.'"';
|
---|
403 | }
|
---|
404 | }
|
---|
405 | $code.= '>';
|
---|
406 | }
|
---|
407 | }
|
---|
408 |
|
---|
409 | // on continue tant qu'il y a du code à analyser...
|
---|
410 | if (isset($this->code[$k+1]))
|
---|
411 | $k++;
|
---|
412 | else
|
---|
413 | $end = true;
|
---|
414 | }
|
---|
415 |
|
---|
416 | // retourne la position finale et le code HTML extrait
|
---|
417 | return array($k, $code);
|
---|
418 | }
|
---|
419 | }
|
---|
420 | }
|
---|
421 | ?>
|
---|