See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 39 and 401]
1 <?php 2 3 namespace Box\Spout\Reader\Common; 4 5 use Box\Spout\Reader\Wrapper\XMLReader; 6 7 /** 8 * Class XMLProcessor 9 * Helps process XML files 10 */ 11 class XMLProcessor 12 { 13 /* Node types */ 14 const NODE_TYPE_START = XMLReader::ELEMENT; 15 const NODE_TYPE_END = XMLReader::END_ELEMENT; 16 17 /* Keys associated to reflection attributes to invoke a callback */ 18 const CALLBACK_REFLECTION_METHOD = 'reflectionMethod'; 19 const CALLBACK_REFLECTION_OBJECT = 'reflectionObject'; 20 21 /* Values returned by the callbacks to indicate what the processor should do next */ 22 const PROCESSING_CONTINUE = 1; 23 const PROCESSING_STOP = 2; 24 25 /** @var \Box\Spout\Reader\Wrapper\XMLReader The XMLReader object that will help read sheet's XML data */ 26 protected $xmlReader; 27 28 /** @var array Registered callbacks */ 29 private $callbacks = []; 30 31 /** 32 * @param \Box\Spout\Reader\Wrapper\XMLReader $xmlReader XMLReader object 33 */ 34 public function __construct($xmlReader) 35 { 36 $this->xmlReader = $xmlReader; 37 } 38 39 /** 40 * @param string $nodeName A callback may be triggered when a node with this name is read 41 * @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END] 42 * @param callable $callback Callback to execute when the read node has the given name and type 43 * @return XMLProcessor 44 */ 45 public function registerCallback($nodeName, $nodeType, $callback) 46 { 47 $callbackKey = $this->getCallbackKey($nodeName, $nodeType); 48 $this->callbacks[$callbackKey] = $this->getInvokableCallbackData($callback); 49 50 return $this; 51 } 52 53 /** 54 * @param string $nodeName Name of the node 55 * @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END] 56 * @return string Key used to store the associated callback 57 */ 58 private function getCallbackKey($nodeName, $nodeType) 59 { 60 return "$nodeName$nodeType"; 61 } 62 63 /** 64 * Because the callback can be a "protected" function, we don't want to use call_user_func() directly 65 * but instead invoke the callback using Reflection. This allows the invocation of "protected" functions. 66 * Since some functions can be called a lot, we pre-process the callback to only return the elements that 67 * will be needed to invoke the callback later. 68 * 69 * @param callable $callback Array reference to a callback: [OBJECT, METHOD_NAME] 70 * @return array Associative array containing the elements needed to invoke the callback using Reflection 71 */ 72 private function getInvokableCallbackData($callback) 73 { 74 $callbackObject = $callback[0]; 75 $callbackMethodName = $callback[1]; 76 $reflectionMethod = new \ReflectionMethod(\get_class($callbackObject), $callbackMethodName); 77 $reflectionMethod->setAccessible(true); 78 79 return [ 80 self::CALLBACK_REFLECTION_METHOD => $reflectionMethod, 81 self::CALLBACK_REFLECTION_OBJECT => $callbackObject, 82 ]; 83 } 84 85 /** 86 * Resumes the reading of the XML file where it was left off. 87 * Stops whenever a callback indicates that reading should stop or at the end of the file. 88 * 89 * @throws \Box\Spout\Reader\Exception\XMLProcessingException 90 * @return void 91 */ 92 public function readUntilStopped() 93 { 94 while ($this->xmlReader->read()) { 95 $nodeType = $this->xmlReader->nodeType; 96 $nodeNamePossiblyWithPrefix = $this->xmlReader->name; 97 $nodeNameWithoutPrefix = $this->xmlReader->localName; 98 99 $callbackData = $this->getRegisteredCallbackData($nodeNamePossiblyWithPrefix, $nodeNameWithoutPrefix, $nodeType); 100 101 if ($callbackData !== null) { 102 $callbackResponse = $this->invokeCallback($callbackData, [$this->xmlReader]); 103 104 if ($callbackResponse === self::PROCESSING_STOP) { 105 // stop reading 106 break; 107 } 108 } 109 } 110 } 111 112 /** 113 * @param string $nodeNamePossiblyWithPrefix Name of the node, possibly prefixed 114 * @param string $nodeNameWithoutPrefix Name of the same node, un-prefixed 115 * @param int $nodeType Type of the node [NODE_TYPE_START || NODE_TYPE_END] 116 * @return array|null Callback data to be used for execution when a node of the given name/type is read or NULL if none found 117 */ 118 private function getRegisteredCallbackData($nodeNamePossiblyWithPrefix, $nodeNameWithoutPrefix, $nodeType) 119 { 120 // With prefixed nodes, we should match if (by order of preference): 121 // 1. the callback was registered with the prefixed node name (e.g. "x:worksheet") 122 // 2. the callback was registered with the un-prefixed node name (e.g. "worksheet") 123 $callbackKeyForPossiblyPrefixedName = $this->getCallbackKey($nodeNamePossiblyWithPrefix, $nodeType); 124 $callbackKeyForUnPrefixedName = $this->getCallbackKey($nodeNameWithoutPrefix, $nodeType); 125 $hasPrefix = ($nodeNamePossiblyWithPrefix !== $nodeNameWithoutPrefix); 126 127 $callbackKeyToUse = $callbackKeyForUnPrefixedName; 128 if ($hasPrefix && isset($this->callbacks[$callbackKeyForPossiblyPrefixedName])) { 129 $callbackKeyToUse = $callbackKeyForPossiblyPrefixedName; 130 } 131 132 // Using isset here because it is way faster than array_key_exists... 133 return isset($this->callbacks[$callbackKeyToUse]) ? $this->callbacks[$callbackKeyToUse] : null; 134 } 135 136 /** 137 * @param array $callbackData Associative array containing data to invoke the callback using Reflection 138 * @param array $args Arguments to pass to the callback 139 * @return int Callback response 140 */ 141 private function invokeCallback($callbackData, $args) 142 { 143 $reflectionMethod = $callbackData[self::CALLBACK_REFLECTION_METHOD]; 144 $callbackObject = $callbackData[self::CALLBACK_REFLECTION_OBJECT]; 145 146 return $reflectionMethod->invokeArgs($callbackObject, $args); 147 } 148 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body