/woops-src/classes/Woops/Xml/Parser.class.php

https://github.com/macmade/WOOPS · PHP · 279 lines · 132 code · 62 blank · 85 comment · 19 complexity · cd7ad691f9ee7efc300492ffcc07291c MD5 · raw file

  1. <?php
  2. ################################################################################
  3. # #
  4. # WOOPS - Web Object Oriented Programming System #
  5. # #
  6. # COPYRIGHT NOTICE #
  7. # #
  8. # Copyright (C) 2009 Jean-David Gadina - www.xs-labs.com #
  9. # All rights reserved #
  10. ################################################################################
  11. # $Id$
  12. /**
  13. * XML parser class
  14. *
  15. * @author Jean-David Gadina - www.xs-labs.com
  16. * @version 1.0
  17. * @package Woops.Xml
  18. */
  19. class Woops_Xml_Parser extends Woops_Core_Object
  20. {
  21. /**
  22. * The minimum version of PHP required to run this class (checked by the WOOPS class manager)
  23. */
  24. const PHP_COMPATIBLE = '5.2.0';
  25. /**
  26. * The supported output charsets
  27. */
  28. protected static $_charsets = array(
  29. 'ISO-8859-1' => true,
  30. 'US-ASCII' => true,
  31. 'UTF-8' => true
  32. );
  33. /**
  34. * The processing instruction handlers
  35. */
  36. protected static $_piHandlers = array();
  37. /**
  38. * The XML parser object
  39. */
  40. protected $_parser = NULL;
  41. /**
  42. * The root XML tag object
  43. */
  44. protected $_xml = NULL;
  45. /**
  46. * The current XML tag object
  47. */
  48. protected $_currentElement = NULL;
  49. /**
  50. * Class constructor
  51. *
  52. * @param string The path of the file to parse
  53. * @param string A prefix path to add to all 'src' and 'href' attributes, if relative
  54. * @param string The output charset (default is UTF-8)
  55. * @return void
  56. */
  57. public function __construct( $file, $charset = 'UTF-8' )
  58. {
  59. // The charset must be uppercase
  60. $charset = strtoupper( $charset );
  61. // Checks if the file exists
  62. if( !file_exists( $file ) || !is_file( $file ) ) {
  63. // The file does not exist
  64. throw new Woops_Xml_Parser_Exception(
  65. 'The specified XML file (\'' . $file . '\') does not exist',
  66. Woops_Xml_Parser_Exception::EXCEPTION_NO_FILE
  67. );
  68. }
  69. // Checks if the file is readable
  70. if( !is_readable( $file ) ) {
  71. // Cannot read the file
  72. throw new Woops_Xml_Parser_Exception(
  73. 'The specified XML file (\'' . $file . '\') is not readable',
  74. Woops_Xml_Parser_Exception::EXCEPTION_FILE_NOT_READABLE
  75. );
  76. }
  77. // Checks if the charset is supported
  78. if( !isset( self::$_charsets[ $charset ] ) ) {
  79. // Unsupported charset
  80. throw new Woops_Xml_Parser_Exception(
  81. 'The specified charset (' . $charset . ') is not supported',
  82. Woops_Xml_Parser_Exception::EXCEPTION_INVALID_CHARSET
  83. );
  84. }
  85. // Creates an XML parser
  86. $this->_parser = xml_parser_create( $charset );
  87. // Sets the current instance as the XML parser object
  88. xml_set_object( $this->_parser, $this );
  89. // Disables case-folding
  90. xml_parser_set_option( $this->_parser, XML_OPTION_CASE_FOLDING, false );
  91. // Sets the element handler methods
  92. xml_set_element_handler( $this->_parser, '_startElementHandler', '_endElementHandler' );
  93. // Sets the character data handler method
  94. xml_set_character_data_handler( $this->_parser, '_characterDataHandler' );
  95. // Sets the processing instruction handler method
  96. xml_set_processing_instruction_handler( $this->_parser, '_processingInstructionHandler' );
  97. // Sets the default data handler method
  98. xml_set_default_handler( $this->_parser, '_defaultHandler' );
  99. // Tries to open a file handler
  100. if( ( $fileHandler = fopen( $file, 'r' ) ) ) {
  101. // Reads data from the file
  102. while( $data = fread( $fileHandler, 4096 ) ) {
  103. // Tries to parse the data
  104. if( !xml_parse( $this->_parser, $data, feof( $fileHandler ) ) ) {
  105. // Gets the error string and line number
  106. $errorString = xml_error_string(xml_get_error_code( $this->_parser ) );
  107. $errorLine = xml_get_current_line_number( $this->_parser );
  108. // Throws an exception, as we have an XML error
  109. throw new Woops_Xml_Parser_Exception(
  110. 'XML parser error: ' . $errorString . ' at line number ' . $errorLine,
  111. Woops_Xml_Parser_Exception::EXCEPTION_XML_PARSER_ERROR
  112. );
  113. }
  114. }
  115. // Closes the file handler
  116. fclose( $fileHandler );
  117. }
  118. // Frees the parser
  119. xml_parser_free( $this->_parser );
  120. }
  121. /**
  122. *
  123. */
  124. public static function registerProcessingInstructionHandler( $name, $className )
  125. {
  126. if( isset( self::$_piHandlers[ $name ] ) ) {
  127. throw new Woops_Xml_Parser_Exception(
  128. 'The processing instruction \'' . $name . '\' is already registered',
  129. Woops_Xml_Parser_Exception::EXCEPTION_PI_EXISTS
  130. );
  131. }
  132. if( !class_exists( $className ) ) {
  133. throw new Woops_Xml_Parser_Exception(
  134. 'Cannot register unexisting class \'' . $className . '\' as a processing instruction handler',
  135. Woops_Xml_Parser_Exception::EXCEPTION_NO_PI_CLASS
  136. );
  137. }
  138. $interfaces = class_implements( $className );
  139. if( !is_array( $interfaces )
  140. || !isset( $interfaces[ 'Woops_Xml_ProcessingInstruction_Handler_Interface' ] )
  141. ) {
  142. throw new Woops_Xml_Parser_Exception(
  143. 'The class \'' . $className . '\' is not a valid processing instruction handler, since it does not implement the \'Woops_Xml_ProcessingInstruction_Handler_Interface\' interface',
  144. Woops_Xml_Parser_Exception::EXCEPTION_INVALID_PI_CLASS
  145. );
  146. }
  147. self::$_piHandlers[ $name ] = $className;
  148. }
  149. /**
  150. *
  151. */
  152. protected function _startElementHandler( $parser, $name, $attribs )
  153. {
  154. if( !is_object( $this->_xml ) ) {
  155. $this->_xml = new Woops_Xml_Tag( $name );
  156. $this->_currentElement = $this->_xml;
  157. } else {
  158. $this->_currentElement = $this->_currentElement->$name;
  159. }
  160. foreach( $attribs as $key => $value ) {
  161. $this->_currentElement[ $key ] = $value;
  162. }
  163. }
  164. /**
  165. *
  166. */
  167. protected function _endElementHandler( $parser, $name )
  168. {
  169. $this->_currentElement = $this->_currentElement->getParent();
  170. }
  171. /**
  172. *
  173. */
  174. protected function _characterDataHandler( $parser, $data )
  175. {
  176. if( trim( $data ) !== '' ) {
  177. $this->_currentElement->addTextData( $data );
  178. }
  179. }
  180. /**
  181. *
  182. */
  183. protected function _processingInstructionHandler( $parser, $name, $data )
  184. {
  185. if( isset( self::$_piHandlers[ $name ] ) ) {
  186. $handlerClass = self::$_piHandlers[ $name ];
  187. $handler = new $handlerClass();
  188. $piParams = preg_split( '/="|" |"$/', $data );
  189. array_pop( $piParams );
  190. $piParamsLength = count( $piParams );
  191. $options = new stdClass();
  192. for( $i = 0; $i < $piParamsLength; $i += 2 ) {
  193. if( isset( $piParams[ $i + 1 ] ) ) {
  194. $options->$piParams[ $i ] = $piParams[ $i + 1 ];
  195. }
  196. }
  197. $result = $handler->process( $options );
  198. if( is_object( $result ) && $result instanceof Woops_Xml_Tag ) {
  199. $this->_currentElement->addChildNode( $result );
  200. } else {
  201. $this->_currentElement->addTextData( $result );
  202. }
  203. }
  204. }
  205. /**
  206. *
  207. */
  208. protected function _defaultHandler( $parser, $data )
  209. {}
  210. /**
  211. *
  212. */
  213. public function getXmlObject()
  214. {
  215. return $this->_xml;
  216. }
  217. }