source: spip-zone/_libs_/html2spip/HTML2SPIPEngine.class @ 88968

Last change on this file since 88968 was 88968, checked in by real3t@…, 5 years ago

Version 0.4

File size: 21.7 KB
Line 
1<?php # vim: syntax=php tabstop=2 softtabstop=2 shiftwidth=2 expandtab textwidth=80 autoindent
2# Copyright (C) 2010  Jean-Jacques Puig
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17class HTML2SPIPEngine extends HTMLEngine {
18
19  # Headings related members (H1, H2, H3...)
20  private $headingDepthSpip     = "{{{";
21  private $headingDepthHTML     = null;
22
23  # Lists related members (UL, OL, LI...)
24  private $listType             = array();
25  private $listDepth            = 0;
26
27  # Tables related members (TABLE, TH, TR, TD...)
28  private $tablesStack          = array();
29
30  # Within marks
31  private $withinStack          = array();
32
33  # SPIP Environment
34  private $spipDBResource       = null;
35  private $spipImagesPath       = null;
36
37  # Documents (images) injected in SPIP database
38  public $spipDocumentsIds      = array();
39
40  # Common substitutions
41  protected $tag2typo = array(
42    'br'  => "\n_ ",
43    'hr'  => "\n----",
44    'p'   => "\n\n",
45  );
46
47  # Main substitution rules
48  # protected $attributeOuterSubstitute = array(
49  # );
50
51  protected $attributeInnerSubstitute = array(
52    'align'         => '_align',
53    'style'         => '_style',
54  );
55
56  protected $tagSubstitute = array(
57    # array() as 3rd arg forces detail
58    # display of ignored attributes
59
60    '#document'     => null,
61    'html'          => null,
62    'body'          => null,
63    'meta'          => null,
64    'link'          => null,
65    'style'         => null,
66
67    'script'        => array('_identityS', '_identityE', array('type')),
68    'embed'         => array('_identityS', '_identityE', null),
69    'param'         => array('_identityS', '_identityE', null),
70    'object'        => array('_identityS', '_identityE', null),
71
72    'div'           => array('_div', '_div', array('class', 'style', 'align')),
73
74    'h1'            => array('_hiS', '_hiE', array('class')),
75    'h2'            => array('_hiS', '_hiE', array('class')),
76    'h3'            => array('_hiS', '_hiE', array('class')),
77    'h4'            => array('_hiS', '_hiE', array('class')),
78    'h5'            => array('_hiS', '_hiE', array('class')),
79    'h6'            => array('_hiS', '_hiE', array('class')),
80
81    'p'             => array('_p', '_p', array('class', 'style', 'align')),
82    'br'            => array('_br', ''),
83
84    'ul'            => array('_ulOrOlS', '_ulOrOlE'),
85    'ol'            => array('_ulOrOlS', '_ulOrOlE'),
86    'li'            => array('_li', '_li', array('style')),
87
88      # TABLES related tags
89
90    'table'         => array('_tableS', '_tableE', array(
91                        'summary', 'cellpadding', 'cellspacing',
92                        'border', 'width', 'class', 'style'
93                       )),
94    'caption'       => array('_captionS', '_captionE', array()),
95    'tr'            => array('_trS', '_trE', array('style')),
96    'th'            => array('_thOrTdS', '_thOrTdE', array(
97                        'colspan', 'rowspan', 'width', 'bgcolor', 'scope'
98                       )),
99    'td'            => array('_thOrTdS', '_thOrTdE', array(
100                        'colspan', 'rowspan', 'width', 'style', 'scope'
101                       )),
102
103    'colgroup'      => array('', '', array()),
104    'col'           => array('', '', array()),
105    'thead'         => array('', '', array()),
106    'tbody'         => array('', '', array()),
107    'tfoot'         => array('', '', array()),
108
109
110    'font'          => array('', '', array(
111                        'face', 'size', 'color',
112                       )),
113    'b'             => array('_strongS', '_strongE', array('style')),
114    'strong'        => array('_strongS', '_strongE'),
115    'blockquote'    => array('_quoteS', '_quoteE', array('class')),
116    'code'          => array('_codeS', '_codeE', array('class')),
117    'textarea'      => array('_cadreS', '_cadreE', array('class')),
118    'em'            => array('_emS', '_emE'),
119    'i'             => array('_emS', '_emE'),
120    'span'          => array('_spanS', '_spanE', array('id', 'class')),
121    'hr'            => array("_tag2typo", ''),
122    'u'             => array('', '', array()),
123    'sup'           => array('_supS', '_supE', array()),
124    'sub'           => array('_subS', '_subE', array()),
125    'strike'        => array('_delS', '_delE', array()),
126    'del'           => array('_delS', '_delE', array()),
127
128    'a'             => array('_aS', '_aE', array(
129                        'title', 'href', 'target', 'style', 'name', 'class',
130                        'rel'
131                       )),
132    'img'           => array('_imgS', '', array(
133                        'src', 'align', 'width', 'height', 'alt', 'title',
134                        'hspace', 'vspace', 'border', 'class',
135                       )),
136
137    '#text'         => '_raw',
138    '#cdata-section'=> '_raw',
139    '#comment'      => '_comment',
140  );
141
142  protected function getAlign($align, $part) {
143    switch ($align) {
144      case 'right':
145        return ($part == TAG_START) ? '[/ ' : ' /]';
146
147      case 'center':
148      case 'middle':
149        if (!$this->within('_thOrTd') && !$this->within('_tr'))
150          return ($part == TAG_START) ? '[| ' : ' |]';
151    }
152  }
153
154  # protected function getJustification($attributes, $part) {
155  #   if (
156  #     array_key_exists('style', $attributes)
157  #     && preg_match('/text-align: (center|justify|left|right)/i', $attributes['style'], $matches))
158  #     $align = $matches[1];
159  #   elseif (array_key_exists('align', $attributes))
160  #     $align = $attributes['align'];
161  #   else
162  #     $align = null;
163
164  #   return $this->getAlign($align, $part);
165  # }
166
167  protected function _align($attribute, $value, $tag, $textContent, $part) {
168    return $this->getAlign($value, $part);
169  }
170
171  protected function _style($attribute, $value, $tag, $textContent, $part) {
172      if (preg_match('/text-align: (center|justify|left|right)/i', $value, $matches))
173        return $this->getAlign($matches[1], $part);
174  }
175
176  protected function _identityS($tag, $attributes) {
177    $data = "<$tag";
178
179    foreach($attributes as $name => $value)
180      $data .= " $name=\"$value\"";
181
182    $data .= '>';
183
184    return $data;
185  }
186
187  protected function _identityE($tag) {
188    return "</$tag>";
189  }
190
191  protected function _div($tag, $attributes, $text, $part) {
192    if (!strlen(trim($text)))
193      return;
194
195    if (array_key_exists('class', $attributes)
196        && (preg_match('/\btexteencadre-spip\b/', $attributes['class']))
197        && (preg_match('/\bspip\b/', $attributes['class']))) {
198        $chunk = '_divtextencadre';
199        switch ($part) {
200          case TAG_START:
201            $this->openChunk($chunk);
202            return;
203
204          case TAG_END:
205            $this->closeChunk($chunk);
206            $data = $this->readChunk($chunk);
207            $this->deleteChunk($chunk);
208            return $this->tag2typo['p'] . '[(' . trim($data) . ')]';
209      }
210    }
211  }
212
213  protected function _hiS($tag, $attributes, $text) {
214    $this->pushWithin('_hi');
215
216    if (!strlen(trim($text)))
217      return;
218
219    if ($this->headingDepthHTML != null) {
220      $depth_diff = strcmp($tag, $this->headingDepthHTML);
221      $verity = ($depth_diff == 0) ? 0 : ($depth_diff / abs($depth_diff));
222    } else
223      $verity = 0;
224
225    $this->headingDepthHTML = $tag;
226
227    switch($verity) {
228      case -1:
229        $diff = strcmp($tag, 'h1');
230        $depth = ($diff > 1) ? str_repeat('*', $diff) : '';
231        $this->headingDepthSpip = "{{{" . $depth;
232        break;
233
234      case 1:
235        if ($this->headingDepthSpip == "{{{")
236          $this->headingDepthSpip .= '**';
237        else
238          $this->headingDepthSpip .= '*';
239        break;
240    }
241
242    $this->openChunk($this->headingDepthSpip);
243
244    return $this->headingDepthSpip;
245  }
246
247  protected function _hiE($tag, $attributes, $text) {
248    $this->popWithin('_hi');
249
250    if (!strlen(trim($text)))
251      return;
252
253    $this->push('}}}');
254    $this->closeChunk($this->headingDepthSpip);
255
256    $data = $this->readChunk($this->headingDepthSpip);
257    $data = preg_replace("/\n+_ /", "\n", $data);
258    $data = preg_replace("/\n+/", "}}}\n$this->headingDepthSpip", $data);
259
260    $this->deleteChunk($this->headingDepthSpip);
261
262    return $data;
263  }
264
265  protected function _p($tag, $attributes, $text, $part) {
266    if (!strlen(trim($text)))
267      return;
268
269    return (($part == TAG_START) ? $this->tag2typo[$tag] : '');
270  }
271
272  protected function _br($tag) {
273    if (
274      (!$this->within('_a'))
275      && (!$this->within('_hi'))
276    )
277      return $this->tag2typo[$tag];
278
279    if ($this->within('_hi'))
280      return "\n";
281
282    return;
283  }
284
285  protected function _tag2typo($tag) {
286    return $this->tag2typo[$tag];
287  }
288
289  protected function _ulOrOlS($tag, $attributes, $text) {
290    if (!strlen(trim($text)))
291      return;
292
293    array_unshift($this->listType, $tag);
294    $this->listDepth++;
295  }
296
297  protected function _ulOrOlE($tag, $attributes, $text) {
298    if (!strlen(trim($text)))
299      return;
300
301    array_shift($this->listType);
302    if ($this->listDepth > 0)
303      $this->listDepth--;
304    else
305      return "\n";
306  }
307
308  protected function _li($tag, $attributes, $text, $part) {
309    if (!strlen(trim($text)))
310      return;
311
312    if ($part == TAG_END)
313      return;
314      # return $this->getJustification($attributes, $part);
315
316    if (sizeof($this->listType)) # This should not happen, but remember input comes from users
317      $listType = $this->listType[0];
318    else
319      $listType = 'ul';
320
321    switch ($listType) {
322      case 'ol':
323        return "\n-" . str_repeat('#', $this->listDepth) . ' '; # . $this->getJustification($attributes, $part);
324
325      default:
326        return "\n-" . str_repeat('*', $this->listDepth) . ' '; # . $this->getJustification($attributes, $part);
327    }
328  }
329
330
331  protected function _tableS($tag, $attributes) {
332    array_unshift(
333      $this->tablesStack,
334      array(
335        'row'     => 0,
336        'col'     => 0,
337        'map'     => array(),
338        'newrow'  => true,
339      )
340    );
341
342    if (array_key_exists('summary', $attributes))
343      $this->tablesStack[0]['summary'] = $attributes['summary'];
344
345    return "\n";  # AIDE SPIP: 'Il est impératif de laisser des lignes vides
346                  #   avant et après ce tableau.
347  }
348
349  protected function _tableE() {
350    array_shift($this->tablesStack);
351
352    return "\n";  # AIDE SPIP: 'Il est impératif de laisser des lignes vides
353                  #   avant et après ce tableau.
354  }
355
356  protected function popTableSummary() {
357    if (array_key_exists('summary', $this->tablesStack[0])) {
358      $summary = $this->tablesStack[0]['summary'];
359      unset($this->tablesStack[0]['summary']);
360    } else
361      $summary = '';
362
363    return $summary;
364  }
365
366  protected function _captionS($tag, $attributes, $text) {
367    if (!strlen(trim($text)))
368      return;
369
370    return "\n|| ";
371  }
372
373  protected function _captionE($tag, $attributes, $text) {
374    if (!strlen(trim($text)))
375      return;
376
377    return ' | ' . $this->popTableSummary() . ' ||';
378  }
379
380  protected function _trS() {
381    $this->pushWithin('_tr');
382
383    $this->tablesStack[0]['newrow'] = true;
384    $this->tablesStack[0]['row']++;
385    $this->tablesStack[0]['col'] = 0;
386
387    if (strlen($summary = $this->popTableSummary()))
388      return "\n|| | $summary ||\n";
389    else
390      return "\n";
391  }
392
393  protected function _trE() {
394    $this->popWithin('_tr');
395  }
396
397  protected function _thOrTdS($tag, $attributes) {
398    $this->pushWithin('_thOrTd');
399
400    $this->tablesStack[0]['col']++;
401
402    if ($this->tablesStack[0]['newrow'] == true) {
403      $this->tablesStack[0]['newrow'] = false;
404      $data = '|';
405    } else
406      $data = '';
407
408    while (
409      ($index = $this->tablesStack[0]['col'] . 'x' .  $this->tablesStack[0]['row'])
410      && (array_key_exists(
411        $index,
412        $this->tablesStack[0]['map']
413      ))
414    ) {
415      unset($this->tablesStack[0]['map'][$index]);
416      $data .= ' ^ |';
417      $this->tablesStack[0]['col']++;
418    }
419
420    if ($tag == 'th')
421      $data .= ' {{';
422    else
423      $data .= ' ';
424
425    return $data;
426  }
427
428  protected function _thOrTdE($tag, $attributes) {
429    $this->popWithin('_thOrTd');
430
431    if ($tag == 'th')
432      $data = '}} |';
433    else
434      $data = ' |';
435
436    if (array_key_exists('colspan', $attributes))
437      $colspan = $attributes['colspan'];
438    else
439      $colspan = 1;
440
441    $row = $this->tablesStack[0]['row'];
442
443    if (array_key_exists('rowspan', $attributes)) {
444      $rowspan = $attributes['rowspan'];
445      $col = $this->tablesStack[0]['col'];
446      for ($j = 1; $j < $rowspan; $j++)
447        for ($i = 0; $i < $colspan; $i++)
448          $this->tablesStack[0]['map'][($i + $col) . 'x' . ($j + $row)] = true;
449    }
450
451    for ($i = 2; $i <= $colspan; $i++) {
452      $data .= ' < |';
453      $this->tablesStack[0]['col']++;
454    }
455
456    while (
457      ($index = ($this->tablesStack[0]['col'] + 1) . 'x' .  $row)
458      && (array_key_exists(
459        $index,
460        $this->tablesStack[0]['map']
461      ))
462    ) {
463      unset($this->tablesStack[0]['map'][$index]);
464      $data .= ' ^ |';
465      $this->tablesStack[0]['col']++;
466    }
467
468    return $data;
469  }
470
471  protected function _codeS($tag, $attributes, $text) {
472    if (!strlen(trim($text)))
473      return;
474
475    return ' <code>';
476  }
477
478  protected function _codeE($tag, $attributes, $text) {
479    if (!strlen(trim($text)))
480      return;
481
482    return '</code> ';
483  }
484
485  protected function _cadreS($tag, $attributes, $text) {
486    if (!strlen(trim($text)))
487      return;
488
489    return ' <cadre>';
490  }
491
492  protected function _cadreE($tag, $attributes, $text) {
493    if (!strlen(trim($text)))
494      return;
495
496    return '</cadre> ';
497  }
498
499  protected function _quoteS($tag, $attributes, $text) {
500    if (!strlen(trim($text)))
501      return;
502
503    if (array_key_exists('class', $attributes))
504      if (preg_match('/\bspip_poesie\b/', $attributes['class']))
505        return ' <poesie>';
506
507    return ' <quote>';
508  }
509
510  protected function _quoteE($tag, $attributes, $text) {
511    if (!strlen(trim($text)))
512      return;
513
514    if (array_key_exists('class', $attributes))
515      if (preg_match('/\bspip_poesie\b/', $attributes['class']))
516        return '</poesie> ';
517
518    return '</quote> ';
519  }
520
521  protected function _strongS($tag, $attributes, $text) {
522    if (!strlen(trim($text)))
523      return;
524
525    if (array_key_exists('class', $attributes))
526      if (preg_match('/\bcaractencadre-spip\b/', $attributes['class']))
527        return ' [*';
528
529    return ' {{';
530  }
531
532  protected function _strongE($tag, $attributes, $text) {
533    if (!strlen(trim($text)))
534      return;
535
536    if (array_key_exists('class', $attributes))
537      if (preg_match('/\bcaractencadre-spip\b/', $attributes['class']))
538        return '*] ';
539
540    return '}} ';
541  }
542
543  protected function _emS($tag, $attributes, $text) {
544    if (!strlen(trim($text)))
545      return;
546
547    return ' {';
548  }
549
550  protected function _emE($tag, $attributes, $text) {
551    if (!strlen(trim($text)))
552      return;
553
554    return '} ';
555  }
556
557  protected function _supS($tag, $attributes, $text) {
558    if (!strlen(trim($text)))
559      return;
560
561    return '<sup>';
562  }
563
564  protected function _supE($tag, $attributes, $text) {
565    if (!strlen(trim($text)))
566      return;
567
568    return '</sup>';
569  }
570
571  protected function _subS($tag, $attributes, $text) {
572    if (!strlen(trim($text)))
573      return;
574
575    return '<sub>';
576  }
577
578  protected function _subE($tag, $attributes, $text) {
579    if (!strlen(trim($text)))
580      return;
581
582    return '</sub>';
583  }
584
585  protected function _delS($tag, $attributes, $text) {
586    if (!strlen(trim($text)))
587      return;
588
589    return '<del>';
590  }
591
592  protected function _delE($tag, $attributes, $text) {
593    if (!strlen(trim($text)))
594      return;
595
596    return '</del>';
597  }
598
599  protected function _spanS($tag, $attributes, $text) {
600    if (!strlen(trim($text)))
601      return;
602
603    if (!array_key_exists('class', $attributes))
604      return;
605
606    switch ($attributes['class']) {
607      case 'chapo':
608        if (!$this->within('_a'))
609          return ' [*';
610    }
611  }
612
613  protected function _spanE($tag, $attributes, $text) {
614    if (!strlen(trim($text)))
615      return;
616
617    if (!array_key_exists('class', $attributes))
618      return;
619
620    switch ($attributes['class']) {
621      case 'chapo':
622        if (!$this->within('_a'))
623          return '*] ';
624    }
625  }
626
627  protected function _aS($tag, $attributes, $text) {
628    $this->pushWithin('_a');
629
630    $name = (array_key_exists('name', $attributes))
631            ? $attributes['name']
632            : false;
633
634    $href = (array_key_exists('href', $attributes))
635            ? $attributes['href']
636            : false;
637
638    $has_title = (array_key_exists('title', $attributes))
639            ? true
640            : false;
641
642    $data = '';
643
644    if (strlen(trim($name)))
645      $data .= "[$name<-]";
646
647    if (!strlen($text))
648      return $data;
649
650    if (!($has_title) && (preg_match('/^mailto:/', $href)))
651      return $data;
652
653    return $data. ' [';
654  }
655
656  protected function _aE($tag, $attributes, $text) {
657    $this->popWithin('_a');
658
659    if (!strlen($text))
660      return;
661
662    $href = (array_key_exists('href', $attributes))
663            ? $attributes['href']
664            : false;
665
666    $title = (array_key_exists('title', $attributes))
667            ? $attributes['title']
668            : false;
669
670    $data = '';
671
672    if (!($title) && (preg_match('/^mailto:/', $href)))
673      return;
674
675    if ($title)
676      $data .= "|$title";
677
678    $data .= '->';
679
680    if (!(preg_match('/^mailto:/', $href)))
681      $data .= $href;
682
683    $data .= '] ';
684
685    return $data;
686  }
687
688  protected function _imgS($tag, $attributes) {
689    if (array_key_exists('class', $attributes) &&
690        ($attributes['class'] == 'puce'))
691      return "\n-";
692     
693    if (!array_key_exists('src', $attributes))
694      return;
695
696    $url = $attributes['src'];
697    if (preg_match('|^file:///|', $url) > 0)
698      return;
699
700    switch ($url[0]) {
701      case '/':
702        $url = "http://" . $_SERVER['SERVER_NAME'] . $url;
703        break;
704
705      default:
706        $this->interrupt("Unexpected URL: $href");
707    }
708
709    if (array_key_exists('align', $attributes))
710      $align = '|' . $attributes['align'];
711    else
712      $align = '';
713
714    $image_name = preg_replace('|^.*/([^/]*)$|', '$1', $url);
715
716    $width = array_key_value_if_notEmpty('width', $attributes, 0);
717    $height = array_key_value_if_notEmpty('height', $attributes, 0);
718    $titre = utf8_decode(array_key_value_if_notEmpty('alt', $attributes, $image_name));
719    $descriptif = utf8_decode(array_key_value_if_notEmpty('title', $attributes, $image_name));
720
721    $id = spip_add_document(
722      $url,
723      $width,
724      $height,
725      'image',
726      $titre,
727      $descriptif,
728      $this->spipImagesPath,
729      $this->spipDBResource
730    );
731
732    $this->spipDocumentsIds[] = $id;
733    return "<img$id$align>";
734  }
735
736  protected function _raw($tag, $text) {
737    if ($this->within('_tr'))
738      return trim($text);
739
740    return $text;
741  }
742
743  protected function _comment($tag, $text) {
744    $this->info("Commentaire: $text\n");
745  }
746
747  protected function postProcessing($data, $chunkTitle = 'default') {
748    $replacements = array(
749      "/[ \t]+/"
750        => ' ',
751
752      "/({{{)+/"
753        => "\n\n{{{",
754
755      "/(}}})+/"
756        => "}}}",
757 
758      "/\s{{{}}}/"
759        => '',
760    );
761
762    $spans = array(
763      ' {',   '} ',
764      ' {{',  '}} ',
765      ' [',   '] ',   # hyperlinks
766      ' [*',  '*] ',
767      ' [**', '**]',
768      '[| ',  ' |]',
769      '[/ ',  ' /]',
770    );
771    foreach($spans as $span)
772      $replacements["/(" . preg_quote($span, '/') . ")+/"] = $span;
773
774    for ($i = 0; $i < sizeof($spans); $i += 2)
775      $replacements["/" . preg_quote($spans[$i] . $spans[$i+1], '/') . "/"] = '';
776
777    $replacements[ "/(\n\n)\n+/" ] = "\n\n";
778    $replacements[ "/(\n_ ~)+/" ] = "\n_ ";
779    $replacements[ "/(\n_ \n)+/" ] = "\n\n";
780    $replacements[ "/\n_ $/" ] = "\n";
781
782    foreach($spans as $span) {
783      $ts = preg_quote(trim($span), '/');
784      $replacements[ "/([a-z]') *(" . $ts. "[aehiouy])/i" ] = "$1$2";
785    }
786
787    return preg_replace(
788            array_keys($replacements),
789            array_values($replacements),
790            $data
791    );
792  }
793
794  protected function preProcessing($data) {
795    $data = html_entity_decode($data, ENT_COMPAT, 'UTF-8');
796
797    $replacements = array();
798
799    $block_tags = array('p', 'h[1-6]');
800
801    foreach($block_tags as $bt)
802      $replacements["|(<$bt(\s+[^>]*)?>)\s*([^\s].*[^\s])\s*(</$bt>)|SsiU"]
803        = '$1$3$4';
804
805    $other_rep = array(
806      '|(<td(\s+[^>]*)?>)\s*<p(\s+[^>]*)?>(.*)</p>\s*(</td>)|SsiU'
807        => '$1$4$5',
808
809      '|<br />\s*(<img alt="-" class="puce")|si'
810        => '$1',
811
812      '|<p>&nbsp;</p>|Si'
813        => '',
814
815      "/\s+/S"
816        => ' ',
817
818      "/&nbsp;/S"
819        => '~',
820
821      '|</caption>\s*<|si'
822        => '</caption><',
823    );
824
825    foreach($other_rep as $repp => $repv)
826      $replacements[$repp] = $repv;
827
828    $data = preg_replace(
829      array_keys($replacements),
830      array_values($replacements),
831      $data);
832
833    return $data;
834  }
835
836  public function __construct($spip_db_resource, $images_path) {
837    $this->spipDBResource = $spip_db_resource;
838    $this->spipImagesPath = $images_path;
839  }
840
841  public function addIdentityTags($tags) {
842        foreach ($tags as $tag)
843                $this->tagSubstitute[$tag] =
844                    array('_identityS', '_identityE', null);
845  }
846
847  protected function pushWithin($tag) {
848    array_unshift($this->withinStack, $tag);
849  }
850
851  protected function popWithin($tag) {
852    if ($this->withinStack[0] == $tag)
853      array_shift($this->withinStack);
854    else
855      $this->error("Incorrect nesting tags stack; expected $tag, found "
856                    . $this->withinStack[0]);
857  }
858
859  protected function within($tag) {
860    return in_array($tag, $this->withinStack);
861  }
862
863}
864
865?>
Note: See TracBrowser for help on using the repository browser.