root/trunk/patForms/Parser.php

Revision 345, 37.3 kB (checked in by argh, 3 years ago)

Small bugfixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?PHP
2 /**
3  * class to parse an HTML document or patTemplate and extract all
4  * patForms elements. They will be replaced by placeholders.
5  *
6  * $Id$
7  *
8  * @author        Stephan Schmidt <schst@php-tools.net>
9  * @package        patForms
10  * @subpackage    Parser
11  * @license        LGPL
12  * @copyright    PHP Application Tools <http://www.php-tools.net>
13  */
14
15 /**
16  * file does not exist
17  */
18 define( 'PATFORMS_PARSER_ERROR_FILE_NOT_FOUND', 100000 );
19  
20 /**
21  * file could not be created
22  */
23 define( 'PATFORMS_PARSER_ERROR_FILE_NOT_CREATED', 100001 );
24
25 /**
26  * element cannot be serialized
27  */
28 define( 'PATFORMS_PARSER_ERROR_ELEMENT_NOT_SERIALIZEABLE', 100002 );
29
30 /**
31  * element cannot be serialized
32  */
33 define( 'PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID', 100003 );
34
35 /**
36  * no namespace has been declared
37  */
38 define( 'PATFORMS_PARSER_ERROR_NO_NAMESPACE', 100004 );
39
40 /**
41  * static property does not exist
42  */
43 define( 'PATFORMS_PARSER_ERROR_NO_STATIC_PROPERTY', 100005 );
44
45 /**
46  * basedir is not valid
47  */
48 define( 'PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID', 100006 );
49
50 /**
51  * no closing tag found
52  */
53 define( 'PATFORMS_PARSER_ERROR_NO_CLOSING_TAG', 100007 );
54
55 /**
56  * invalid tag found
57  */
58 define( 'PATFORMS_PARSER_ERROR_INVALID_CLOSING_TAG', 100008 );
59
60 /**
61  * invalid tag found
62  */
63 define( 'PATFORMS_PARSER_ERROR_DRIVER_FILE_NOT_FOUND', 100009 );
64
65 /**
66  * invalid tag found
67  */
68 define( 'PATFORMS_PARSER_ERROR_DRIVER_CLASS_NOT_FOUND', 100010 );
69
70 /**
71  * form does not exist
72  */
73 define( 'PATFORMS_PARSER_ERROR_FORM_NOT_FOUND', 100011 );
74
75 /**
76  * unknown tag in custom namespace
77  */
78 define('PATFORMS_PARSER_ERROR_UNKNOWN_TAG', 100020);
79
80 /**
81  * static properties
82  * @var     array
83  * @access    private
84  */ 
85 $GLOBALS['_patForms_Parser']    =    array(
86                                         'cacheFolder'                =>    false,
87                                         'baseDir'                    =>    false,
88                                         'namespace'                    =>    false,
89                                         'placeholder'                =>    '{PATFORMS_ELEMENT_%s}',
90                                         'placeholder_form_start'    =>    '{PATFORMS_FORM_%s_START}',
91                                         'placeholder_form_end'        =>    '{PATFORMS_FORM_%s_END}',
92                                         'placeholder_case'            =>    'upper',
93                                         'namespaceHandlers'            =>    array()
94                                     );
95
96
97 /**
98  * class to parse an HTML document or patTemplate and extract all
99  * patForms elements. They will be replaced by placeholders.
100  *
101  * It is possible to attach handlers for other namespaces.
102  * The parser will delegate the tags to these handlers and
103  * the return values will be used to create the form instead.
104  *
105  * Known issues of the parser:
106  * - Currently it's only possible to parse one form per document, this will change in future versions
107  *
108  * @author        Stephan Schmidt <s.schmidt@metrix.de>
109  * @package        patForms
110  * @subpackage    Parser
111  * @license        LGPL
112  * @copyright    PHP Application Tools <http://www.php-tools.net>
113  */
114 class patForms_Parser
115 {
116    /**
117     * Stores the names of all static properties that patForms_Parser will use as defaults
118     * for the properties with the same name on startup.
119     *
120     * @access    private
121     */
122     var $staticProperties    =    array(
123         'cacheFolder'    =>    'setCacheDir',
124         'baseDir'        =>    'setBaseDir',
125         'namespace'        =>    'setNamespace',
126     );
127
128    /**
129     * namespace for form elements
130     * @var        string
131     * @access    private
132     */
133     var $_namespace            =    null;
134
135    /**
136     * namespace handlers
137     * @var        string
138     * @access    private
139     */
140     var $_namespaceHandlers    =    array();
141
142    /**
143     * cache folder
144     * @var        string
145     * @access    private
146     */
147     var $_cacheFolder    =    null;
148
149    /**
150     * base directory
151     * @var        string
152     * @access    private
153     */
154     var $_baseDir    =    null;
155
156    /**
157     * placeholder template for form elements
158     *
159     * %s will be replaced with the name of the element
160     *
161     * @var        string
162     * @access    private
163     * @see        $_placeholder_case
164     */
165     var $_placeholder = '{PATFORMS_ELEMENT_%s}';
166
167    /**
168     * placeholder template for start of form
169     *
170     * %s will be replaced with the name of the form
171     *
172     * @var        string
173     * @access    private
174     * @see        $_placeholder_case
175     * @see        $_placeholder_form_end
176     */
177     var $_placeholder_form_start = '{PATFORMS_FORM_%s_START}';
178
179    /**
180     * placeholder template for start of form
181     *
182     * %s will be replaced with the name of the form
183     *
184     * @var        string
185     * @access    private
186     * @see        $_placeholder_case
187     * @see        $_placeholder_form_start
188     */
189     var $_placeholder_form_end = '{PATFORMS_FORM_%s_END}';
190
191    /**
192     * case of the element name in the template
193     *
194     * @var        string
195     * @access    private
196     * @see        $_placeholder
197     */
198     var $_placeholder_case = 'upper';
199
200    /**
201     * sourcefile name
202     * @var        string
203     * @access    private
204     */
205     var $_sourceFile;
206
207    /**
208     * outputfile name
209     * @var        string
210     * @access    private
211     */
212     var $_outputFile;
213
214    /**
215     * form object
216     * @var    object
217     * @access    private
218     */
219     var $_form;
220     
221    /**
222     * name of the current form
223     * @var        string
224     * @access    private
225     */
226     var $_currentForm = '__default';
227
228    /**
229     * form element definitions
230     * @var    array
231     * @access    private
232     */
233     var $_elementDefinitions = array();
234
235    /**
236     * form attributes
237     * @var    array
238     * @access    private
239     */
240     var $_formAttributes = array();
241     
242    /**
243     * HTML code
244     * @access    private
245     * @var      string
246     */
247     var $_html;
248
249    /**
250     * elements found during parsing process
251     * @access    private
252     * @var    array
253     */
254     var    $_elStack = array();
255
256    /**
257     * cdata found during parsing process
258     * @access    private
259     * @var    array
260     */
261     var    $_cData    = array();
262
263    /**
264     * tag depth
265     * @access    private
266     * @var    integer
267     */
268     var    $_depth    =    0;
269
270    /**
271     * entities that may be used in attributes
272     * @var        array
273     * @access    private
274     */
275     var $_entities    =    array(
276                                 '&quot;' => '"',
277                                 '&amp;'  => '&',
278                                 '&apos;' => '\'',
279                                 '&gt;'   => '>',
280                                 '&lt;'   => '<',
281                             );
282
283    /**
284     * constructor
285     *
286     * @access    public
287     */
288     function patForms_Parser()
289     {
290         $this->__construct();   
291     }
292     
293    /**
294     * constructor
295     *
296     * @access    public
297     */
298     function __construct()
299     {
300         foreach ($this->staticProperties as $staticProperty => $setMethod) {
301             $propValue = patForms_Parser::getStaticProperty( $staticProperty );
302             if (patErrorManager::isError($propValue)) {
303                 continue;
304             }
305             
306             $this->$setMethod($propValue);
307         }
308
309         /**
310          * set the placeholders
311          */
312         $this->setPlaceholder( patForms_Parser::getStaticProperty( 'placeholder' ), patForms_Parser::getStaticProperty( 'placeholder_case' ) );
313         $this->setFormPlaceholders( patForms_Parser::getStaticProperty( 'placeholder_form_start' ), patForms_Parser::getStaticProperty( 'placeholder_form_end' ) );
314         
315         // configure namespace handler
316         $nsHandlers    = &patForms_Parser::getStaticProperty('namespaceHandlers');
317         $namespaces    = array_keys( $nsHandlers );
318         foreach ($namespaces as $ns) {
319             $this->addNamespace( $ns, $nsHandlers[$ns] );
320         }
321     }
322     
323    /**
324     * setCacheDir
325     *
326     * The cache dir has to be set to utilize the caching features.
327     *
328     * @access    public
329     * @param    mixed        path to the directory or false to disable caching
330     * @return    boolean        true on success
331     * @see    $_cacheFolder
332     */
333     function setCacheDir( $dir )
334     {
335         if ($dir != false) {
336             if (!is_dir($dir) || !is_writable($dir)) {
337                 return patErrorManager::raiseError(
338                     PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID,
339                     "Cache folder '$dir' is either no directory or not writable.",
340                     'Check path and permissions'
341                 );
342             }
343         }
344
345         if (isset($this) && is_a($this, 'patForms_Parser')) {
346             $this->_cacheFolder    = $dir;
347         } else {
348             patForms_Parser::setStaticProperty('cacheFolder', $dir);
349         }
350         return  true;
351     }
352     
353     /**
354      * Set base directory for all files.
355      *
356      * @access    public
357      * @param    mixed    path to the directory or false reset the basedir
358      * @return boolean $result    true on success
359      * @see    $_cacheFolder
360      */
361     function setBaseDir( $dir )
362     {
363         if( $dir != false )
364         {
365             if( !is_dir( $dir ) )
366             {
367                 return patErrorManager::raiseError(
368                     PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID,
369                     "Base directory '$dir' is does not exist or is no directory"
370                 );
371             }
372         }
373         
374         if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
375         {
376             $this->_baseDir    =    $dir;
377         }
378         else
379         {
380             patForms_Parser::setStaticProperty( "baseDir", $dir );
381         }
382         
383         return  true;
384     }
385     
386    /**
387     * Set the namespace for the form elements
388     *
389     * If this method is called statically, it will set the
390     * namespace for future static calls to createFormFromTemplate()
391     * and parseFile().
392     *
393     * If the namespace is set to null, patForms_Parser will try
394     * to get the namespace declaration from the (X)HTML document
395     * that is being parsed.
396     *
397     * That means that you should include a
398     * xmlns:myForm="http://www.php-tools.net/patForms/basic"
399     * attribute in the root tag of your templates.
400     * "myForm" is the namespace that you are using for your
401     * patForms elements. Make sure that you are using the
402     * correct URI for the attribute so it can be recognized.
403     *
404     * @access    public
405     * @param    string    namespace
406     * @see        getNamespacePrefix()
407     */
408     function setNamespace( $ns )
409     {
410         if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
411         {
412             $this->_namespace    =    $ns;
413         }
414         else
415         {
416             patForms_Parser::setStaticProperty( "namespace", $ns );
417         }
418     }
419     
420    /**
421     * Set the placeholder template for elements.
422     *
423     * When parsing an HTML page that contains patForms elements
424     * they will be replaced by placeholders. This method allows
425     * you to set the format of the placeholders.
426     *
427     * You may specify a format string like you would for
428     * sprintf, with one %s that marks where the name of the
429     * element will be inserted.
430     *
431     * @access    public
432     * @param    string        placeholder
433     * @param    string        flag to indicate, whether the name should be inserted in uppercase ('upper'),
434     *                        lowercase ('lower') or how it was specified ('keep').
435     * @see        sprintf()
436     * @see        setFormPlaceholders()
437     */
438     function setPlaceholder( $placeholder, $case = "upper" )
439     {
440         if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
441         {
442             $this->_placeholder            =    $placeholder;
443             $this->_placeholder_case    =    $case;
444         }
445         else
446         {
447             patForms_Parser::setStaticProperty( "placeholder", $placeholder );
448             patForms_Parser::setStaticProperty( "placeholder_case", $case );
449         }
450     }
451     
452    /**
453     * Set the placeholder template for form start and end tag.
454     *
455     * When parsing an HTML page that contains a patForms:Form tag
456     * this will be replaced by placeholders. This method allows
457     * you to set the format of the placeholders.
458     *
459     * You may specify a format string like you would for
460     * sprintf, with one %s that marks where the name of the
461     * element will be inserted.
462     *
463     * @access    public
464     * @param    string        placeholder for the start tag
465     * @param    string        placeholder for the end tag
466     * @see        sprintf()
467     * @see        setPlaceholder()
468     */
469     function setFormPlaceholders( $start, $end )
470     {
471         if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
472         {
473             $this->_placeholder_form_start    =    $start;
474             $this->_placeholder_form_end    =    $end;
475         }
476         else
477         {
478             patForms_Parser::setStaticProperty( "placeholder_form_start", $start );
479             patForms_Parser::setStaticProperty( "placeholder_form_end", $end );
480         }
481     }
482     
483    /**
484     * add a namespace handler
485     *
486     * Namespace handlers can be used to include external
487     * data in patForms elements.
488     *
489     * @access    public
490     * @param    string    namespace
491     * @param    object    handler
492     */
493     function addNamespace( $namespace, &$handler )
494     {
495         if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
496         {
497             $this->_namespaceHandlers[$namespace]    =&    $handler;
498         }
499         else
500         {
501             $ns    =&    patForms_Parser::getStaticProperty( 'namespaceHandlers' );
502             $ns[$namespace]    =&    $handler;
503         }
504     }
505
506    /**
507     * parse a file and extract all elements
508     *
509     * If an outputfile is specified and the cache directory
510     * has been set, two files will be created:
511     * - the outputfile, containing the HTML code with placeholders for all elements
512     * - a cache file that contains a serialized string with the attributes of all elements
513     *
514     * On the next call to parseFile() patForms_Parser will check
515     * if these files exist and use them instead of parsing the sourcefile.
516     *
517     * @access    public
518     * @param    string    $filename    filename
519     * @return    boolean
520     * @uses        parseString()
521     */
522     function parseFile( $filename, $outputFile = null )
523     {
524         $this->_sourceFile    =    $filename;
525         $this->_outputFile    =    $outputFile;
526
527         if ($this->_outputFile != null) {
528             $cache = $this->_checkCache();
529             if ($cache) {
530                 return true;
531             }
532         }
533                 
534         $string    = file_get_contents( $this->_adjustFilename( $this->_sourceFile ) );
535         if ($string === false) {
536             $relative = '(no basedir set)';
537             if( !empty( $this->_baseDir ) ) {
538                 $relative = '(relative to "'.$this->_baseDir.'")';
539             }
540             
541             return patErrorManager::raiseError(
542                 PATFORMS_PARSER_ERROR_FILE_NOT_FOUND,
543                 'Sourcefile could not be read',
544                 'Tried to open file "'.$this->_sourceFile.'" '.$relative
545             );
546         }
547
548         $result    = $this->parseString($string);
549
550         if ($this->_outputFile != null && $this->_cacheFolder != null) {
551             $success = $this->_writeHTMLToFile();
552             if (patErrorManager::isError($success)) {
553                 return $success;
554             }
555                 
556             $success = $this->_writeFormToFile();
557             if (patErrorManager::isError($success)) {
558                 return $success;
559             }
560         }
561         return    $result;
562     }
563
564    /**
565     * write the resulting HTML code to a file
566     *
567     * @access    private
568     */
569     function _writeHTMLToFile()
570     {
571         return    $this->_writeToFile( $this->_adjustFilename( $this->_outputFile ), $this->getHTML() );
572     }
573     
574    /**
575     * write the form defintions to a cache file
576     *
577     * @access    private
578     */
579     function _writeFormToFile()
580     {
581         $tmp        =    array(
582                                 'attributes'    =>    $this->_formAttributes,
583                                 'elements'        =>    $this->_elementDefinitions
584                             );
585         $formDef    =    serialize( $tmp );
586         $cacheFile    =    $this->_getFormCacheFilename();
587         return    $this->_writeToFile( $cacheFile, $formDef );
588     }
589
590    /**
591     * get the filename for the form cache
592     *
593     * @access    private
594     * @return    string
595     */
596     function _getFormCacheFilename()
597     {
598         return    $this->_cacheFolder . "/" . md5( $this->_sourceFile ) . ".form";
599     }
600
601    /**
602     * adjust a filename according to the specified basedir
603     *
604     * @access    private
605     * @param    string        filename
606     * @param    string        adjusted filename
607     */
608     function _adjustFilename( $filename )
609     {
610         if( !empty( $this->_baseDir ) )
611             return    "{$this->_baseDir}/$filename";
612         return $filename;
613     }
614     
615    /**
616     * write a string to a file
617     *
618     * I dreamed of a world, where PHP5 has already been declared as
619     * stable and I could just use file_put_contents() for such an easy
620     * task.
621     *
622     * @access    private
623     * @param    string    $filename
624     * @param    string    $data
625     */
626     function _writeToFile( $file, $data )
627     {
628         $fp = @fopen( $file, "w" );
629         if (!$fp) {
630             return patErrorManager::raiseError(
631                 PATFORMS_PARSER_ERROR_FILE_NOT_CREATED,
632                 "Could not create file '$file'.",
633                 "File: " . $file
634             );
635         }
636         flock($fp, LOCK_EX);
637         fwrite($fp, $data);
638         flock($fp, LOCK_UN);
639         fclose($fp);
640         return true;
641     }
642     
643     
644    /**
645     * check, whether a cache can be used
646     *
647     * @access    private
648     * @return    boolean        true, if the cache exists and still is valid, false otherwise
649     */
650     function _checkCache()
651     {
652         if( empty( $this->_cacheFolder ) )
653             return    false;
654             
655         if( !file_exists( $this->_outputFile ) )
656             return    false;
657
658         $cacheFile    =    $this->_getFormCacheFilename();
659             
660         if( !file_exists( $cacheFile ) )
661             return    false;
662
663         $srcFile    =    $this->_adjustFilename( $this->_sourceFile );
664         $srcTime    =    filemtime( $srcFile );
665             
666         if( filemtime