source: trunk/client/inc/hpdf/parsingHTML.class.php@ 269

Last change on this file since 269 was 2, checked in by root, 15 years ago

importo il progetto

File size: 11.6 KB
Line 
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
12if (!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(' ', '&nbsp;', $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?>
Note: See TracBrowser for help on using the repository browser.