PageRenderTime 61ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/patForms/Parser.php

https://github.com/chregu/fluxcms
PHP | 1611 lines | 788 code | 170 blank | 653 comment | 125 complexity | ff5048a4bda9181c4a0ea5360d71d8bf MD5 | raw file
Possible License(s): GPL-2.0, BSD-3-Clause, Apache-2.0, LGPL-2.1
  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. * file does not exist
  16. */
  17. define( "PATFORMS_PARSER_ERROR_FILE_NOT_FOUND", 100000 );
  18. /**
  19. * file could not be created
  20. */
  21. define( "PATFORMS_PARSER_ERROR_FILE_NOT_CREATED", 100001 );
  22. /**
  23. * element cannot be serialized
  24. */
  25. define( "PATFORMS_PARSER_ERROR_ELEMENT_NOT_SERIALIZEABLE", 100002 );
  26. /**
  27. * element cannot be serialized
  28. */
  29. define( "PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID", 100003 );
  30. /**
  31. * no namespace has been declared
  32. */
  33. define( "PATFORMS_PARSER_ERROR_NO_NAMESPACE", 100004 );
  34. /**
  35. * static property does not exist
  36. */
  37. define( "PATFORMS_PARSER_ERROR_NO_STATIC_PROPERTY", 100005 );
  38. /**
  39. * basedir is not valid
  40. */
  41. define( "PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID", 100006 );
  42. /**
  43. * no closing tag found
  44. */
  45. define( "PATFORMS_PARSER_ERROR_NO_CLOSING_TAG", 100007 );
  46. /**
  47. * invalid tag found
  48. */
  49. define( "PATFORMS_PARSER_ERROR_INVALID_CLOSING_TAG", 100008 );
  50. /**
  51. * invalid tag found
  52. */
  53. define( "PATFORMS_PARSER_ERROR_DRIVER_FILE_NOT_FOUND", 100009 );
  54. /**
  55. * invalid tag found
  56. */
  57. define( "PATFORMS_PARSER_ERROR_DRIVER_CLASS_NOT_FOUND", 100010 );
  58. /**
  59. * form does not exist
  60. */
  61. define( "PATFORMS_PARSER_ERROR_FORM_NOT_FOUND", 100011 );
  62. /**
  63. * static properties
  64. * @var array
  65. * @access private
  66. */
  67. $GLOBALS["_patForms_Parser"] = array(
  68. "cacheFolder" => false,
  69. "baseDir" => false,
  70. "namespace" => false,
  71. "placeholder" => "{PATFORMS_ELEMENT_%s}",
  72. "placeholder_form_start" => "{PATFORMS_FORM_%s_START}",
  73. "placeholder_form_end" => "{PATFORMS_FORM_%s_END}",
  74. "placeholder_case" => "upper",
  75. "namespaceHandlers" => array()
  76. );
  77. /**
  78. * class to parse an HTML document or patTemplate and extract all
  79. * patForms elements. They will be replaced by placeholders.
  80. *
  81. * It is possible to attach handlers for other namespaces.
  82. * The parser will delegate the tags to these handlers and
  83. * the return values will be used to create the form instead.
  84. *
  85. * Known issues of the parser:
  86. * - Currently it's only possible to parse one form per document, this will change in future versions
  87. *
  88. * @author Stephan Schmidt <s.schmidt@metrix.de>
  89. * @package patForms
  90. * @subpackage Parser
  91. * @license LGPL
  92. * @copyright PHP Application Tools <http://www.php-tools.net>
  93. * @version 1.0
  94. * @todo unified treatment of CData and children of the elements
  95. * => CData must be treated as children. This will allow
  96. * injection of dynamic content inside any tag.
  97. * @todo implement constructor to get default values from 'static properties'
  98. * @todo replace double quotes with single quotes
  99. */
  100. class patForms_Parser
  101. {
  102. /**
  103. * Stores the names of all static properties that patForms_Parser will use as defaults
  104. * for the properties with the same name on startup.
  105. *
  106. * @access private
  107. */
  108. var $staticProperties = array(
  109. 'cacheFolder' => 'setCacheDir',
  110. 'baseDir' => 'setBaseDir',
  111. 'namespace' => 'setNamespace',
  112. );
  113. /**
  114. * namespace for form elements
  115. * @var string
  116. * @access private
  117. */
  118. var $_namespace = null;
  119. /**
  120. * namespace handlers
  121. * @var string
  122. * @access private
  123. */
  124. var $_namespaceHandlers = array();
  125. /**
  126. * cache folder
  127. * @var string
  128. * @access private
  129. */
  130. var $_cacheFolder = null;
  131. /**
  132. * base directory
  133. * @var string
  134. * @access private
  135. */
  136. var $_baseDir = null;
  137. /**
  138. * placeholder template for form elements
  139. *
  140. * %s will be replaced with the name of the element
  141. *
  142. * @var string
  143. * @access private
  144. * @see $_placeholder_case
  145. */
  146. var $_placeholder = "{PATFORMS_ELEMENT_%s}";
  147. /**
  148. * placeholder template for start of form
  149. *
  150. * %s will be replaced with the name of the form
  151. *
  152. * @var string
  153. * @access private
  154. * @see $_placeholder_case
  155. * @see $_placeholder_form_end
  156. */
  157. var $_placeholder_form_start = "{PATFORMS_FORM_%s_START}";
  158. /**
  159. * placeholder template for start of form
  160. *
  161. * %s will be replaced with the name of the form
  162. *
  163. * @var string
  164. * @access private
  165. * @see $_placeholder_case
  166. * @see $_placeholder_form_start
  167. */
  168. var $_placeholder_form_end = "{PATFORMS_FORM_%s_END}";
  169. /**
  170. * case of the element name in the template
  171. *
  172. * @var string
  173. * @access private
  174. * @see $_placeholder
  175. */
  176. var $_placeholder_case = "upper";
  177. /**
  178. * sourcefile name
  179. * @var string
  180. * @access private
  181. */
  182. var $_sourceFile;
  183. /**
  184. * outputfile name
  185. * @var string
  186. * @access private
  187. */
  188. var $_outputFile;
  189. /**
  190. * form object
  191. * @var object
  192. * @access private
  193. */
  194. var $_form;
  195. /**
  196. * name of the current form
  197. * @var string
  198. * @access private
  199. */
  200. var $_currentForm = '__default';
  201. /**
  202. * form element definitions
  203. * @var array
  204. * @access private
  205. */
  206. var $_elementDefinitions = array();
  207. /**
  208. * form attributes
  209. * @var array
  210. * @access private
  211. */
  212. var $_formAttributes = array();
  213. /**
  214. * HTML code
  215. * @access private
  216. * @var string
  217. */
  218. var $_html;
  219. /**
  220. * elements found during parsing process
  221. * @access private
  222. * @var array
  223. */
  224. var $_elStack = array();
  225. /**
  226. * cdata found during parsing process
  227. * @access private
  228. * @var array
  229. */
  230. var $_cData = array();
  231. /**
  232. * tag depth
  233. * @access private
  234. * @var integer
  235. */
  236. var $_depth = 0;
  237. /**
  238. * entities that may be used in attributes
  239. * @var array
  240. * @access private
  241. */
  242. var $_entities = array(
  243. '&quot;' => '"',
  244. '&amp;' => '&',
  245. '&apos;' => '\'',
  246. '&gt;' => '>',
  247. '&lt;' => '<',
  248. );
  249. /**
  250. * constructor
  251. *
  252. * @access public
  253. */
  254. function patForms_Parser()
  255. {
  256. $this->__construct();
  257. }
  258. /**
  259. * constructor
  260. *
  261. * @access public
  262. */
  263. function __construct()
  264. {
  265. foreach( $this->staticProperties as $staticProperty => $setMethod )
  266. {
  267. $propValue = patForms_Parser::getStaticProperty( $staticProperty );
  268. if( patErrorManager::isError( $propValue ) )
  269. continue;
  270. $this->$setMethod( $propValue );
  271. }
  272. /**
  273. * set the placeholders
  274. */
  275. $this->setPlaceholder( patForms_Parser::getStaticProperty( "placeholder" ), patForms_Parser::getStaticProperty( "placeholder_case" ) );
  276. $this->setFormPlaceholders( patForms_Parser::getStaticProperty( "placeholder_form_start" ), patForms_Parser::getStaticProperty( "placeholder_form_end" ) );
  277. // configure namespace handler
  278. $nsHandlers = &patForms_Parser::getStaticProperty( 'namespaceHandlers' );
  279. $namespaces = array_keys( $nsHandlers );
  280. foreach( $namespaces as $ns )
  281. {
  282. $this->addNamespace( $ns, $nsHandlers[$ns] );
  283. }
  284. }
  285. /**
  286. * setCacheDir
  287. *
  288. * The cache dir has to be set to utilize the caching features.
  289. *
  290. * @access public
  291. * @param mixed path to the directory or false to disable caching
  292. * @return boolean true on success
  293. * @see $_cacheFolder
  294. */
  295. function setCacheDir( $dir )
  296. {
  297. if( $dir != false )
  298. {
  299. if( !is_dir( $dir ) || !is_writable( $dir ) )
  300. {
  301. return patErrorManager::raiseError(
  302. PATFORMS_PARSER_ERROR_CACHEDIR_NOT_VALID,
  303. "Cache folder '$dir' is either no directory or not writable.",
  304. "Check path and permissions"
  305. );
  306. }
  307. }
  308. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  309. {
  310. $this->_cacheFolder = $dir;
  311. }
  312. else
  313. {
  314. patForms_Parser::setStaticProperty( "cacheFolder", $dir );
  315. }
  316. return true;
  317. }
  318. /**
  319. * Set base directory for all files.
  320. *
  321. * @access public
  322. * @param mixed path to the directory or false reset the basedir
  323. * @return boolean $result true on success
  324. * @see $_cacheFolder
  325. */
  326. function setBaseDir( $dir )
  327. {
  328. if( $dir != false )
  329. {
  330. if( !is_dir( $dir ) )
  331. {
  332. return patErrorManager::raiseError(
  333. PATFORMS_PARSER_ERROR_BASEDIR_NOT_VALID,
  334. "Base directory '$dir' is does not exist or is no directory"
  335. );
  336. }
  337. }
  338. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  339. {
  340. $this->_baseDir = $dir;
  341. }
  342. else
  343. {
  344. patForms_Parser::setStaticProperty( "baseDir", $dir );
  345. }
  346. return true;
  347. }
  348. /**
  349. * Set the namespace for the form elements
  350. *
  351. * If this method is called statically, it will set the
  352. * namespace for future static calls to createFormFromTemplate()
  353. * and parseFile().
  354. *
  355. * If the namespace is set to null, patForms_Parser will try
  356. * to get the namespace declaration from the (X)HTML document
  357. * that is being parsed.
  358. *
  359. * That means that you should include a
  360. * xmlns:myForm="http://www.php-tools.net/patForms/basic"
  361. * attribute in the root tag of your templates.
  362. * "myForm" is the namespace that you are using for your
  363. * patForms elements. Make sure that you are using the
  364. * correct URI for the attribute so it can be recognized.
  365. *
  366. * @access public
  367. * @param string namespace
  368. * @see getNamespacePrefix()
  369. */
  370. function setNamespace( $ns )
  371. {
  372. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  373. {
  374. $this->_namespace = $ns;
  375. }
  376. else
  377. {
  378. patForms_Parser::setStaticProperty( "namespace", $ns );
  379. }
  380. }
  381. /**
  382. * Set the placeholder template for elements.
  383. *
  384. * When parsing an HTML page that contains patForms elements
  385. * they will be replaced by placeholders. This method allows
  386. * you to set the format of the placeholders.
  387. *
  388. * You may specify a format string like you would for
  389. * sprintf, with one %s that marks where the name of the
  390. * element will be inserted.
  391. *
  392. * @access public
  393. * @param string placeholder
  394. * @param string flag to indicate, whether the name should be inserted in uppercase ('upper'),
  395. * lowercase ('lower') or how it was specified ('keep').
  396. * @see sprintf()
  397. * @see setFormPlaceholders()
  398. */
  399. function setPlaceholder( $placeholder, $case = "upper" )
  400. {
  401. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  402. {
  403. $this->_placeholder = $placeholder;
  404. $this->_placeholder_case = $case;
  405. }
  406. else
  407. {
  408. patForms_Parser::setStaticProperty( "placeholder", $placeholder );
  409. patForms_Parser::setStaticProperty( "placeholder_case", $case );
  410. }
  411. }
  412. /**
  413. * Set the placeholder template for form start and end tag.
  414. *
  415. * When parsing an HTML page that contains a patForms:Form tag
  416. * this will be replaced by placeholders. This method allows
  417. * you to set the format of the placeholders.
  418. *
  419. * You may specify a format string like you would for
  420. * sprintf, with one %s that marks where the name of the
  421. * element will be inserted.
  422. *
  423. * @access public
  424. * @param string placeholder for the start tag
  425. * @param string placeholder for the end tag
  426. * @see sprintf()
  427. * @see setPlaceholder()
  428. */
  429. function setFormPlaceholders( $start, $end )
  430. {
  431. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  432. {
  433. $this->_placeholder_form_start = $start;
  434. $this->_placeholder_form_end = $end;
  435. }
  436. else
  437. {
  438. patForms_Parser::setStaticProperty( "placeholder_form_start", $start );
  439. patForms_Parser::setStaticProperty( "placeholder_form_end", $end );
  440. }
  441. }
  442. /**
  443. * add a namespace handler
  444. *
  445. * Namespace handlers can be used to include external
  446. * data in patForms elements.
  447. *
  448. * @access public
  449. * @param string namespace
  450. * @param object handler
  451. */
  452. function addNamespace( $namespace, &$handler )
  453. {
  454. if( isset( $this ) && is_a( $this, "patForms_Parser" ) )
  455. {
  456. $this->_namespaceHandlers[$namespace] =& $handler;
  457. }
  458. else
  459. {
  460. $ns =& patForms_Parser::getStaticProperty( 'namespaceHandlers' );
  461. $ns[$namespace] =& $handler;
  462. }
  463. }
  464. /**
  465. * parse a file and extract all elements
  466. *
  467. * If an outputfile is specified and the cache directory
  468. * has been set, two files will be created:
  469. * - the outputfile, containing the HTML code with placeholders for all elements
  470. * - a cache file that contains a serialized string with the attributes of all elements
  471. *
  472. * On the next call to parseFile() patForms_Parser will check
  473. * if these files exist and use them instead of parsing the sourcefile.
  474. *
  475. * @access public
  476. * @param string $filename filename
  477. * @return boolean
  478. * @uses parseString()
  479. */
  480. function parseFile( $filename, $outputFile = null )
  481. {
  482. $this->_sourceFile = $filename;
  483. $this->_outputFile = $outputFile;
  484. if( $this->_outputFile != null )
  485. {
  486. $cache = $this->_checkCache();
  487. if( $cache )
  488. return true;
  489. }
  490. $string = $this->file_get_contents( $this->_adjustFilename( $this->_sourceFile ) );
  491. if( $string === false )
  492. {
  493. return patErrorManager::raiseError(
  494. PATFORMS_PARSER_ERROR_FILE_NOT_FOUND,
  495. "Sourcefile could not be read",
  496. "Sourcefile: " . $this->_sourceFile
  497. );
  498. }
  499. $result = $this->parseString( $string );
  500. if( $this->_outputFile != null && $this->_cacheFolder != null )
  501. {
  502. $success = $this->_writeHTMLToFile();
  503. if( patErrorManager::isError( $success ) )
  504. return $success;
  505. $success = $this->_writeFormToFile();
  506. if( patErrorManager::isError( $success ) )
  507. return $success;
  508. }
  509. return $result;
  510. }
  511. /**
  512. * write the resulting HTML code to a file
  513. *
  514. * @access private
  515. */
  516. function _writeHTMLToFile()
  517. {
  518. return $this->_writeToFile( $this->_adjustFilename( $this->_outputFile ), $this->getHTML() );
  519. }
  520. /**
  521. * write the form defintions to a cache file
  522. *
  523. * @access private
  524. */
  525. function _writeFormToFile()
  526. {
  527. $tmp = array(
  528. 'attributes' => $this->_formAttributes,
  529. 'elements' => $this->_elementDefinitions
  530. );
  531. $formDef = serialize( $tmp );
  532. $cacheFile = $this->_getFormCacheFilename();
  533. return $this->_writeToFile( $cacheFile, $formDef );
  534. }
  535. /**
  536. * get the filename for the form cache
  537. *
  538. * @access private
  539. * @return string
  540. */
  541. function _getFormCacheFilename()
  542. {
  543. return $this->_cacheFolder . "/" . md5( $this->_sourceFile ) . ".form";
  544. }
  545. /**
  546. * adjust a filename according to the specified basedir
  547. *
  548. * @access private
  549. * @param string filename
  550. * @param string adjusted filename
  551. */
  552. function _adjustFilename( $filename )
  553. {
  554. if( !empty( $this->_baseDir ) )
  555. return "{$this->_baseDir}/$filename";
  556. return $filename;
  557. }
  558. /**
  559. * write a string to a file
  560. *
  561. * I dreamed of a world, where PHP5 has already been declared as
  562. * stable and I could just use file_put_contents() for such an easy
  563. * task.
  564. *
  565. * @access private
  566. * @param string $filename
  567. * @param string $data
  568. */
  569. function _writeToFile( $file, $data )
  570. {
  571. $fp = @fopen( $file, "w" );
  572. if( !$fp )
  573. {
  574. return patErrorManager::raiseError(
  575. PATFORMS_PARSER_ERROR_FILE_NOT_CREATED,
  576. "Could not create file.",
  577. "File: " . $file
  578. );
  579. }
  580. flock( $fp, LOCK_EX );
  581. fwrite( $fp, $data );
  582. flock( $fp, LOCK_UN );
  583. fclose( $fp );
  584. return true;
  585. }
  586. /**
  587. * check, whether a cache can be used
  588. *
  589. * @access private
  590. * @return boolean true, if the cache exists and still is valid, false otherwise
  591. */
  592. function _checkCache()
  593. {
  594. if( empty( $this->_cacheFolder ) )
  595. return false;
  596. if( !file_exists( $this->_outputFile ) )
  597. return false;
  598. $cacheFile = $this->_getFormCacheFilename();
  599. if( !file_exists( $cacheFile ) )
  600. return false;
  601. $srcFile = $this->_adjustFilename( $this->_sourceFile );
  602. $srcTime = filemtime( $srcFile );
  603. if( filemtime( $this->_outputFile ) < $srcTime )
  604. return false;
  605. if( filemtime( $cacheFile ) < $srcTime )
  606. return false;
  607. $form = $this->file_get_contents( $cacheFile );
  608. if( $form === false )
  609. return false;
  610. $tmp = unserialize( $form );
  611. $this->_formAttributes = $tmp['attributes'];
  612. $this->_elementDefinitions = $tmp['elements'];
  613. return true;
  614. }
  615. /**
  616. * parse a string and extract all elements
  617. *
  618. * @access public
  619. * @param string $string html string that should be parsed
  620. * @return boolean
  621. */
  622. function parseString( $string )
  623. {
  624. $this->_elStack = array();
  625. $this->_cData = array();
  626. $this->_depth = 0;
  627. // has namespace been set?
  628. if( $this->_namespace == null )
  629. {
  630. $ns = $this->getNamespacePrefix( $string );
  631. if( patErrorManager::isError( $ns ) )
  632. {
  633. return $ns;
  634. }
  635. $this->setNamespace( $ns );
  636. }
  637. $knownNamespaces = array_merge( array( $this->_namespace ), array_keys( $this->_namespaceHandlers ) );
  638. $regexp = "/(<(\/?)([[:alnum:]]+):([[:alnum:]]+)[[:space:]]*([^>]*)>)/im";
  639. $tokens = preg_split( $regexp, $string, -1, PREG_SPLIT_DELIM_CAPTURE );
  640. /**
  641. * the first token is always character data
  642. * Though it could just be empty
  643. */
  644. if( $tokens[0] != '' )
  645. $this->_characterData( $tokens[0] );
  646. $cnt = count( $tokens );
  647. $i = 1;
  648. // process all tokens
  649. while( $i < $cnt )
  650. {
  651. $fullTag = $tokens[$i++];
  652. $closing = $tokens[$i++];
  653. $namespace = $tokens[$i++];
  654. $tagname = $tokens[$i++];
  655. $attString = $tokens[$i++];
  656. $empty = substr( $attString, -1 );
  657. $data = $tokens[$i++];
  658. /**
  659. * check, whether it's a known namespace
  660. */
  661. if( !in_array( $namespace, $knownNamespaces ) )
  662. {
  663. $this->_characterData( $fullTag );
  664. $this->_characterData( $data );
  665. continue;
  666. }
  667. /**
  668. * is it a closing tag?
  669. */
  670. if( $closing == "/" )
  671. {
  672. $result = $this->_endElement( $namespace, $tagname );
  673. if( patErrorManager::isError( $result ) )
  674. {
  675. return $result;
  676. }
  677. $this->_characterData( $data );
  678. continue;
  679. }
  680. /**
  681. * Is empty or opening tag!
  682. */
  683. $attributes = $this->_parseAttributes( $attString );
  684. $result = $this->_startElement( $namespace, $tagname, $attributes );
  685. if( patErrorManager::isError( $result ) )
  686. {
  687. return $result;
  688. }
  689. /**
  690. * check, if the tag is empty
  691. */
  692. if( $empty == "/" )
  693. {
  694. $result = $this->_endElement( $namespace, $tagname );
  695. if( patErrorManager::isError( $result ) )
  696. {
  697. return $result;
  698. }
  699. }
  700. $this->_characterData( $data );
  701. }
  702. /**
  703. * check for tags that are still open
  704. */
  705. if( $this->_depth > 0 )
  706. {
  707. $el = array_pop( $this->_elStack );
  708. return patErrorManager::raiseError(
  709. PATFORMS_PARSER_ERROR_NO_CLOSING_TAG,
  710. "No closing tag for {$el['ns']}:{$el['name']} found."
  711. );
  712. }
  713. return true;
  714. }
  715. /**
  716. * parse an attribute string and build an array
  717. *
  718. * @access private
  719. * @param string attribute string
  720. * @param array attribute array
  721. */
  722. function _parseAttributes( $string )
  723. {
  724. // Check for trailing slash, if tag was an empty XML Tag
  725. if( substr( $string, -1 ) == "/" )
  726. $string = trim( substr( $string, 0, strlen( $string )-1 ) );
  727. $attributes = array();
  728. preg_match_all('/([a-zA-Z_\:]+)="((?:\\\.|[^"\\\])*)"/', $string, $match);
  729. for ($i = 0; $i < count($match[1]); $i++)
  730. {
  731. $attributes[strtolower( $match[1][$i] )] = strtr( (string)$match[2][$i], $this->_entities );
  732. }
  733. return $attributes;
  734. }
  735. /**
  736. * start element handler
  737. *
  738. * @access private
  739. * @param string namespace
  740. * @param string local part
  741. * @param array attributes
  742. * @see endElement()
  743. */
  744. function _startElement( $ns, $name, $attributes )
  745. {
  746. array_push( $this->_elStack, array(
  747. "ns" => $ns,
  748. "name" => $name,
  749. "attributes" => $attributes,
  750. "hasChildren" => false
  751. )
  752. );
  753. switch( $name )
  754. {
  755. /**
  756. * Build a form
  757. */
  758. case 'Form':
  759. if (isset($attributes['name'])) {
  760. $this->_currentForm = $attributes['name'];
  761. }
  762. $this->_formAttributes[$this->_currentForm] = $attributes;
  763. $this->_characterData( $this->_getPlaceholderForForm( $attributes['name'], 'start' ) );
  764. break;
  765. }
  766. $this->_depth++;
  767. $this->_data[$this->_depth] = '';
  768. }
  769. /**
  770. * end element handler
  771. *
  772. * @access private
  773. * @param string namespace
  774. * @param string local part
  775. * @uses addElementDefinition() to add an element to the form
  776. * @uses _callNamespaceHandler() to delegate callback to a handler
  777. */
  778. function _endElement( $ns, $name )
  779. {
  780. $el = array_pop( $this->_elStack );
  781. $data = $this->_getCData();
  782. $this->_depth--;
  783. if( $el["name"] != $name || $el["ns"] != $ns )
  784. {
  785. return patErrorManager::raiseError(
  786. PATFORMS_PARSER_ERROR_INVALID_CLOSING_TAG,
  787. "Invalid closing tag {$ns}:{$name}. {$el['ns']}:{$el['name']} expected."
  788. );
  789. }
  790. /**
  791. * Foreign namespace has been found
  792. * delegate it to the namespace handler
  793. */
  794. if( $ns != $this->_namespace )
  795. {
  796. $result = $this->_callNamespaceHandler( $ns, $name, $el["attributes"], $data );
  797. if( $this->_depth > 0 )
  798. {
  799. $this->_addToParentTag( $result );
  800. }
  801. elseif( is_string( $result ) )
  802. {
  803. $this->_characterData( $result );
  804. }
  805. return true;
  806. }
  807. switch( $name )
  808. {
  809. /**
  810. * Build a form
  811. */
  812. case "Form":
  813. $this->_characterData( $data );
  814. $this->_currentForm = '__default';
  815. $this->_characterData( $this->_getPlaceholderForForm( $el['attributes']['name'], 'end' ) );
  816. break;
  817. /**
  818. * Add an option to an Enum element
  819. */
  820. case "Option":
  821. $parent = array_pop( $this->_elStack );
  822. array_push( $this->_elStack, $parent );
  823. $parentName = strtolower( $parent['name'] );
  824. if( isset( $el["attributes"]["value"] ) )
  825. {
  826. if( !isset( $el['attributes']['id'] ) )
  827. {
  828. if( !isset( $parent['children'] ) )
  829. $parent['children'] = array();
  830. $cnt = count( $parent['children'] ) + 1;
  831. $el['attributes']['id'] = $parent['attributes']['name'] . '_option' . $cnt;
  832. }
  833. $label = isset( $el["attributes"]["label"] ) ? $el["attributes"]["label"] : $data;
  834. $id = isset( $el["attributes"]["id"] ) ? $el["attributes"]["id"] : $el['attributes']['value'];
  835. $option = $el['attributes'];
  836. $option['label'] = $label;
  837. $option['id'] = $id;
  838. }
  839. else
  840. {
  841. $option = $data;
  842. }
  843. $this->_addToParentTag( $option, null, true );
  844. /**
  845. * if it is an option of a radio group, add a placeholder
  846. *
  847. */
  848. if( $parentName == 'radiogroup' )
  849. {
  850. $this->_characterData( '{PATFORMS_ELEMENT_'.strtoupper( $parent['attributes']['name'] ).'_' . strtoupper( $el['attributes']['id'] ) . '}' );
  851. }
  852. break;
  853. /**
  854. * Use a datasoucre
  855. */
  856. case "Datasource":
  857. switch( strtolower( $el["attributes"]["type"] ) )
  858. {
  859. /**
  860. * Datasource is a callback
  861. * This can either be a function or a static method
  862. */
  863. case "callback":
  864. /**
  865. * get method from method or function attribute
  866. */
  867. if( isset( $el["attributes"]["method"] ) )
  868. $method = $el["attributes"]["method"];
  869. elseif( isset( $el["attributes"]["function"] ) )
  870. $method = $el["attributes"]["function"];
  871. else
  872. die( "No callback method specified." );
  873. if( isset( $el["attributes"]["class"] ) )
  874. {
  875. $datasource = array( $el["attributes"]["class"], $method );
  876. }
  877. else
  878. {
  879. $datasource = $method;
  880. }
  881. break;
  882. /**
  883. * add a custom datasource
  884. */
  885. case "custom":
  886. $datasource = $el["children"];
  887. break;
  888. }
  889. $this->_addToParentAttributes( "datasource", $datasource );
  890. break;
  891. /**
  892. * Set any attribute
  893. */
  894. case "Attribute":
  895. if( isset( $el["children"] ) )
  896. {
  897. $data = $el["children"];
  898. }
  899. $this->_addToParentAttributes( $el["attributes"]["name"], $data );
  900. break;
  901. /**
  902. * Adjust a radio-group => use the children as values
  903. */
  904. case "RadioGroup":
  905. $el['type'] = $el['name'];
  906. unset($el['name']);
  907. if( isset( $el["children"] ) ) {
  908. if( is_array( $el["children"] ) ) {
  909. $el["attributes"]["values"] = $el["children"];
  910. unset($el['children']);
  911. }
  912. }
  913. $renderer = null;
  914. // for the radio group, we use the string renderer as renderer, but
  915. // only if a template is available at all - if there is none, we
  916. // simply let the radiogroup render itself the default way.
  917. $template = trim( $data );
  918. if( !empty( $template ) ) {
  919. $el['renderer'] =& patForms::createRenderer( 'String' );
  920. $el['renderer']->setTemplate( $data );
  921. $el['renderer']->setPlaceholder( '{PATFORMS_ELEMENT_'.strtoupper( $el['attributes']['name'] ).'_%s}' );
  922. }
  923. if (count($this->_elStack) > 1) {
  924. $this->_addToParentTag($el, $el['attributes']['name'], true);
  925. } else {
  926. $this->addElementDefinitionByArray( $el );
  927. }
  928. $this->_characterData( $this->_getPlaceholderForElement( $el['attributes']['name'] ) );
  929. break;
  930. /**
  931. * Adjust an enum => use the children as values
  932. */
  933. case "Enum":
  934. case "Set":
  935. $el['type'] = $el['name'];
  936. unset($el['name']);
  937. if( isset( $el["children"] ) ) {
  938. if( is_array( $el["children"] ) ) {
  939. $el["attributes"]["values"] = $el["children"];
  940. unset($el['children']);
  941. }
  942. }
  943. if (count($this->_elStack) > 1) {
  944. $this->_addToParentTag($el, $el['attributes']['name'], true);
  945. } else {
  946. $this->addElementDefinitionByArray( $el );
  947. }
  948. $this->_characterData( $this->_getPlaceholderForElement( $el["attributes"]["name"] ) );
  949. break;
  950. /**
  951. * No reserved value, treat it as an
  952. * element and add it to the definitions
  953. */
  954. default:
  955. $el['type'] = $el['name'];
  956. unset($el['name']);
  957. // add a renderer to the Group
  958. if (strtolower($el['type']) === 'group') {
  959. $el['renderer'] = patForms::createRenderer('String');
  960. $el['renderer']->setTemplate($data);
  961. $el['renderer']->setPlaceholder('{'.$this->_placeholder.'}', 'name');
  962. }
  963. if (count($this->_elStack) > 1) {
  964. $this->_addToParentTag($el, $el['attributes']['name'], true);
  965. } else {
  966. $this->addElementDefinitionByArray( $el );
  967. }
  968. $this->_characterData( $this->_getPlaceholderForElement( $el["attributes"]["name"] ) );
  969. break;
  970. }
  971. }
  972. /**
  973. * call a method in a namespace handler
  974. *
  975. * @access private
  976. * @param string namespace
  977. * @param string tag name, will be used as method name
  978. * @param array attributes of the tag
  979. * @param mixed content of the tag
  980. * @todo implement error handling
  981. */
  982. function &_callNamespaceHandler( $ns, $name, $attributes, $data )
  983. {
  984. if( !method_exists( $this->_namespaceHandlers[$ns], $name ) ) {
  985. die( "Unknown tag $name in namespace $ns." );
  986. }
  987. $result = &$this->_namespaceHandlers[$ns]->$name( $attributes, $data );
  988. return $result;
  989. }
  990. /**
  991. * add an element to the element definition list
  992. *
  993. * The element definition list is used to build the form
  994. * and also serialized for caching
  995. *
  996. * @access public
  997. * @param string name
  998. * @param string type
  999. * @param array attributes
  1000. * @param object renderer
  1001. * @todo allow more than one element with the same name
  1002. */
  1003. function addElementDefinition( $name, $type, $attributes, $renderer = null )
  1004. {
  1005. if (!isset($this->_elementDefinitions[$this->_currentForm])) {
  1006. $this->_elementDefinitions[$this->_currentForm] = array();
  1007. }
  1008. $this->_elementDefinitions[$this->_currentForm][$name] = array(
  1009. "type" => $type,
  1010. "attributes" => $attributes,
  1011. "renderer" => &$renderer
  1012. );
  1013. return true;
  1014. }
  1015. /**
  1016. * add an element to the element definition list
  1017. *
  1018. * The element definition list is used to build the form
  1019. * and also serialized for caching
  1020. *
  1021. * @access public
  1022. * @param string name
  1023. * @param string type
  1024. * @param array attributes
  1025. * @param object renderer
  1026. * @todo allow more than one element with the same name
  1027. */
  1028. function addElementDefinitionByArray( $def )
  1029. {
  1030. if (!isset($this->_elementDefinitions[$this->_currentForm])) {
  1031. $this->_elementDefinitions[$this->_currentForm] = array();
  1032. }
  1033. $name = $def['attributes']['name'];
  1034. $this->_elementDefinitions[$this->_currentForm][$name] = $def;
  1035. return true;
  1036. }
  1037. /**
  1038. * cdata handler
  1039. *
  1040. * @access private
  1041. * @param string data
  1042. */
  1043. function _characterData( $data )
  1044. {
  1045. if( $this->_depth == 0 ) {
  1046. $this->_html .= $data;
  1047. return true;
  1048. }
  1049. $this->_data[$this->_depth] .= $data;
  1050. }
  1051. /**
  1052. * get the character data of the element
  1053. *
  1054. * @access private
  1055. * @return string
  1056. */
  1057. function _getCData()
  1058. {
  1059. if( $this->_depth == 0 ) {
  1060. return '';
  1061. }
  1062. return $this->_data[$this->_depth];
  1063. }
  1064. /**
  1065. * adds an attribute to the parent tag
  1066. *
  1067. * @access private
  1068. * @param string attribute name
  1069. * @param mixed attribute value
  1070. */
  1071. function _addToParentAttributes( $name, $value )
  1072. {
  1073. $parent = array_pop( $this->_elStack );
  1074. $parent["attributes"][$name] = $value;
  1075. array_push( $this->_elStack, $parent );
  1076. return true;
  1077. }
  1078. /**
  1079. * add child element to parant tag
  1080. *
  1081. * This is used to build enum lists or groups.
  1082. * In the definition of the
  1083. *
  1084. * @access private
  1085. * @param mixed child to add, normally is an array
  1086. * @param string key of the child
  1087. * @param boolean defines whether the element always has more the one child.
  1088. * If set to true the first element will be stored in an array
  1089. * @return boolean success, currently always true
  1090. */
  1091. function _addToParentTag( $child, $key = null, $hasMultiple = false )
  1092. {
  1093. // get the parent tag
  1094. $parent = array_pop( $this->_elStack );
  1095. // check if there already are children
  1096. if( !$parent["hasChildren"] )
  1097. {
  1098. $parent["hasChildren"] = true;
  1099. /**
  1100. * no key defined => just set this as only child
  1101. */
  1102. if( $key == null && !$hasMultiple )
  1103. {
  1104. $parent["children"] = $child;
  1105. array_push( $this->_elStack, $parent );
  1106. return true;
  1107. }
  1108. else
  1109. {
  1110. $parent["children"] = array();
  1111. }
  1112. }
  1113. // if a key has been supplied
  1114. if( $key != null )
  1115. $parent["children"][$key] = $child;
  1116. else
  1117. array_push( $parent["children"], $child );
  1118. array_push( $this->_elStack, $parent );
  1119. return true;
  1120. }
  1121. /**
  1122. * get the name of the parent tag
  1123. *
  1124. * @access private
  1125. * @return string tag name
  1126. */
  1127. function _getParentName()
  1128. {
  1129. $parent = array_pop( $this->_elStack );
  1130. array_push( $this->_elStack, $parent );
  1131. return $parent['name'];
  1132. }
  1133. /**
  1134. * get the placeholder for an element
  1135. *
  1136. * @access protected
  1137. * @param string element name
  1138. * @param string name of the placeholder template
  1139. * @return string placeholder
  1140. */
  1141. function _getPlaceholderForElement( $element, $template = 'placeholder' )
  1142. {
  1143. // adjust the case
  1144. switch( $this->_placeholder_case )
  1145. {
  1146. case "upper":
  1147. $element = strtoupper( $element );
  1148. break;
  1149. case "lower":
  1150. $element = strtolower( $element );
  1151. break;
  1152. default:
  1153. break;
  1154. }
  1155. return sprintf( $this->{'_'.$template}, $element );
  1156. }
  1157. /**
  1158. * get the placeholder for a form tag
  1159. *
  1160. * @access protected
  1161. * @param string name of the form
  1162. * @param string type (start|end)
  1163. * @return string placeholder
  1164. */
  1165. function _getPlaceholderForForm( $form, $type )
  1166. {
  1167. // adjust the case
  1168. switch( $this->_placeholder_case )
  1169. {
  1170. case 'upper':
  1171. $form = strtoupper( $form );
  1172. break;
  1173. case 'lower':
  1174. $form = strtolower( $form );
  1175. break;
  1176. default:
  1177. break;
  1178. }
  1179. $template = '_placeholder_form_'.$type;
  1180. return sprintf( $this->$template, $form );
  1181. }
  1182. /**
  1183. * get the one form element
  1184. *
  1185. * @access public
  1186. * @param string element name
  1187. * @return object patForms element
  1188. * @deprecated Please use getForm() instead and fetch the elements from the form
  1189. * @todo fix this method! It is totally broken as patForms_Parser now may contain more than one form. Best solution would be removing the method if nobody objects
  1190. */
  1191. function &getFormElement( $name )
  1192. {
  1193. return $this->_form->getElement( $name );
  1194. }
  1195. /**
  1196. * get the complete form object
  1197. *
  1198. * @access public
  1199. * @return object patForms object
  1200. */
  1201. function &getForm($name = null)
  1202. {
  1203. if ($name === null && count($this->_elementDefinitions) === 1) {
  1204. reset($this->_elementDefinitions);
  1205. $name = key($this->_elementDefinitions);
  1206. }
  1207. if ($name === null) {
  1208. if (!is_object($this->_form)) {
  1209. require_once PATFORMS_INCLUDE_PATH . '/Collection.php';
  1210. $this->_form = new patForms_Collection();
  1211. }
  1212. foreach ($this->_elementDefinitions as $formName => $elementDefinitions) {
  1213. if ($this->_form->containsForm($formName)) {
  1214. continue;
  1215. }
  1216. $this->_form->addForm( patForms::createForm( $elementDefinitions, $this->_formAttributes[$formName] ) );
  1217. }
  1218. return $this->_form;
  1219. }
  1220. if (is_object( $this->_form[$name] )) {
  1221. return $this->_form[$name];
  1222. }
  1223. if (!isset($this->_elementDefinitions[$name])) {
  1224. return patErrorManager::raiseError(PATFORMS_PARSER_ERROR_FORM_NOT_FOUND, 'The form does not exist.');
  1225. }
  1226. if (!isset($this->_formAttributes[$name])) {
  1227. $this->_formAttributes[$name] = array(
  1228. 'name' => 'form'
  1229. );
  1230. }
  1231. $this->_form[$name] = &patForms::createForm( $this->_elementDefinitions[$name], $this->_formAttributes[$name] );
  1232. return $this->_form[$name];
  1233. }
  1234. /**
  1235. * get the HTML code where all form elements have been replaced with variables
  1236. *
  1237. * @access public
  1238. * @return string HTML code
  1239. */
  1240. function getHTML()
  1241. {
  1242. if( $this->_html != null )
  1243. return $this->_html;
  1244. if( $this->_outputFile == null )
  1245. return false;
  1246. $this->_html = $this->file_get_contents( $this->_adjustFilename( $this->_outputFile ) );
  1247. if( $this->_html === false )
  1248. {
  1249. return patErrorManager::raiseError(
  1250. PATFORMS_PARSER_ERROR_FILE_NOT_FOUND,
  1251. "Sourcefile could not be read",
  1252. "Sourcefile: " . $this->_sourceFile
  1253. );
  1254. }
  1255. return $this->_html;
  1256. }
  1257. /**
  1258. * create a new parser
  1259. *
  1260. * Use this method to create a specialty
  1261. * parser, that uses a driver.
  1262. *
  1263. * This could include a parser that is a renderer
  1264. * at the same time.
  1265. *
  1266. * @static
  1267. * @access public
  1268. * @param mixed driver name
  1269. * @return object patForms_Parser
  1270. */
  1271. static function &createParser( $driver = null )
  1272. {
  1273. // not based on any driver
  1274. if( $driver == null )
  1275. {
  1276. $parser = &new patForms_Parser;
  1277. }
  1278. else
  1279. {
  1280. $driverFile = dirname( __FILE__ ) . "/Parser/{$driver}.php";
  1281. if( !@include_once( $driverFile ) )
  1282. {
  1283. return patErrorManager::raiseError(
  1284. PATFORMS_PARSER_ERROR_DRIVER_FILE_NOT_FOUND,
  1285. "Driver file '$driverFile' could not be loaded, file not found"
  1286. );
  1287. }
  1288. $parserClass = "patForms_Parser_{$driver}";
  1289. if( !class_exists( $parserClass ) )
  1290. {
  1291. return patErrorManager::raiseError(
  1292. PATFORMS_PARSER_ERROR_DRIVER_CLASS_NOT_FOUND,
  1293. "Driver file has been loaded correctly, but the according driver class '$parserClass' could not be found"
  1294. );
  1295. }
  1296. $parser = &new $parserClass;
  1297. }
  1298. return $parser;
  1299. }
  1300. /**
  1301. * Create a form from an FTMPL (Form Template) file.
  1302. *
  1303. * This method will return a form object, that already
  1304. * contains a reference to the parser, that can be used
  1305. * as a renderer for the form.
  1306. *
  1307. * @access public
  1308. * @static
  1309. * @param string driver for the parser, use null for the default parser
  1310. * @param string form template to create the form
  1311. * @param string outputfile for the resulting HTML code without form elements
  1312. * @return object patForms object
  1313. */
  1314. function &createFormFromTemplate( $driver, $formTemplate, $outputFile = null )
  1315. {
  1316. // create a new parser
  1317. $parser =& patForms_Parser::createParser( $driver );
  1318. $success =& $parser->parseFile( $formTemplate, $outputFile );
  1319. if( patErrorManager::isError( $success ) )
  1320. return $success;
  1321. $form =& $parser->getForm();
  1322. $form->setRenderer( $parser, array( 'includeElements' => true ) );
  1323. return $form;
  1324. }
  1325. /**
  1326. * get the namespace of patForms elements from the
  1327. * html page.
  1328. *
  1329. * To make this method work, you will need an
  1330. * xmlns:foo="http://www.php-tools.net/patForms/basic"
  1331. * attribute in any tag of your page.
  1332. *
  1333. * @access public
  1334. * @param string $html HTML document that should be parsed
  1335. * @return string namespace or a patError
  1336. */
  1337. function getNamespacePrefix( $html )
  1338. {
  1339. $regExp = '~xmlns:([^=]+)=["\']http://www.php-tools.net/patForms/basic["\']~im';
  1340. $matches = array();
  1341. $result = preg_match( $regExp, $html, $matches );
  1342. if( $result )
  1343. {
  1344. return $matches[1];
  1345. }
  1346. return patErrorManager::raiseError(
  1347. PATFORMS_PARSER_ERROR_NO_NAMESPACE,
  1348. "No namespace for patForms declared."
  1349. );
  1350. }
  1351. /**
  1352. * Set a static property.
  1353. *
  1354. * Static properties are stored in an array in a global variable,
  1355. * until PHP5 is ready to use.
  1356. *
  1357. * @static
  1358. * @param string property name
  1359. * @param string property value
  1360. * @see getStaticProperty()
  1361. */
  1362. function setStaticProperty( $property, &$value )
  1363. {
  1364. $GLOBALS["_patForms_Parser"][$property] = &$value;
  1365. }
  1366. /**
  1367. * Get a static property.
  1368. *
  1369. * Static properties are stored in an array in a global variable,
  1370. * until PHP5 is ready to use.
  1371. *
  1372. * @static
  1373. * @param string property name
  1374. * @return string property value
  1375. * @see setStaticProperty()
  1376. */
  1377. function &getStaticProperty( $property )
  1378. {
  1379. if( isset( $GLOBALS["_patForms_Parser"][$property] ) )
  1380. {
  1381. return $GLOBALS["_patForms_Parser"][$property];
  1382. }
  1383. return patErrorManager::raiseWarning(
  1384. PATFORMS_PARSER_ERROR_NO_STATIC_PROPERTY,
  1385. "Static property '$property' does not exist."
  1386. );
  1387. }
  1388. /**
  1389. * Replacement for file_get_contents
  1390. *
  1391. * This is needed to make patForms work with PHP < 4.3
  1392. *
  1393. * @access public
  1394. * @static
  1395. * @param string filename
  1396. * @return mixed content of the file or false
  1397. */
  1398. function file_get_contents( $filename )
  1399. {
  1400. if( !file_exists( $filename ) )
  1401. return false;
  1402. if( function_exists( "file_get_contents" ) )
  1403. {
  1404. return file_get_contents( $filename );
  1405. }
  1406. $fp = @fopen( $filename, "rb" );
  1407. if( !$fp )
  1408. {
  1409. return false;
  1410. }
  1411. flock( $fp, LOCK_SH );
  1412. $content = fread( $fp, filesize( $filename ) );
  1413. flock( $fp, LOCK_UN );
  1414. fclose( $fp );
  1415. return $content;
  1416. }
  1417. /**
  1418. * Replacement for is_a
  1419. *
  1420. * This is needed to make patForms work with PHP < 4.2
  1421. *
  1422. * @access public
  1423. * @static
  1424. * @param object object to test
  1425. * @param string classname
  1426. * @return boolean true of object is an instance of classname or derived from this class
  1427. */
  1428. function is_a( $object, $classname )
  1429. {
  1430. if( function_exists( "is_a" ) )
  1431. {
  1432. return is_a( $object, $classname );
  1433. }
  1434. if( !is_object( $object ) )
  1435. return false;
  1436. if( get_class( $object ) == strtolower( $classname ) )
  1437. return true;
  1438. return is_subclass_of( $object, $classname );
  1439. }
  1440. }
  1441. ?>