Skip to content
This repository

[hotfix/ZF-3257] Forward-port ZF-3257 and ZF-11385 #356

Closed
wants to merge 2 commits into from

2 participants

Adam Lundrigan Matthew Weier O'Phinney
Adam Lundrigan

Forward-port patches:
ZF-3257 - Attributes or character data lost in Zend_Json::fromXml() in specific circumstances
ZF-11385 - Zend_Json doesn't seem to count recursion correctly

added some commits August 26, 2011
Adam Lundrigan Applies patch for ZF-3257 to correct issue where attributes or charac…
…ter data lost in Zend\Json\Json::fromXml() in specific circumstances
b4d79d5
Adam Lundrigan Integrated unit tests from ZF-11385 which prove that recursion count …
…problem introduced (and subsequently fixed) in ZF-3257
995b043
Matthew Weier O'Phinney
Owner

Reviewed, merged, and pushed to master.

Matthew Weier O'Phinney weierophinney closed this August 30, 2011
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 2 unique commits by 1 author.

Aug 26, 2011
Adam Lundrigan Applies patch for ZF-3257 to correct issue where attributes or charac…
…ter data lost in Zend\Json\Json::fromXml() in specific circumstances
b4d79d5
Adam Lundrigan Integrated unit tests from ZF-11385 which prove that recursion count …
…problem introduced (and subsequently fixed) in ZF-3257
995b043
This page is out of date. Refresh to see the latest.
197  library/Zend/Json/Json.php
@@ -196,6 +196,99 @@ protected static function _recursiveJsonExprFinder(
196 196
         }
197 197
         return $value;
198 198
     }
  199
+    /**
  200
+     * Return the value of an XML attribute text or the text between
  201
+     * the XML tags
  202
+     *
  203
+     * In order to allow Zend_Json_Expr from xml, we check if the node
  204
+     * matchs the pattern that try to detect if it is a new Zend_Json_Expr
  205
+     * if it matches, we return a new Zend_Json_Expr instead of a text node
  206
+     *
  207
+     * @param SimpleXMLElement $simpleXmlElementObject
  208
+     * @return Zend_Json_Expr|string
  209
+     */
  210
+    protected static function _getXmlValue($simpleXmlElementObject) {
  211
+        $pattern = '/^[\s]*new Zend_Json_Expr[\s]*\([\s]*[\"\']{1}(.*)[\"\']{1}[\s]*\)[\s]*$/';
  212
+        $matchings = array();
  213
+        $match = preg_match ($pattern, $simpleXmlElementObject, $matchings);
  214
+        if ($match) {
  215
+            return new Expr($matchings[1]);
  216
+        } else {
  217
+            return (trim(strval($simpleXmlElementObject)));
  218
+        }
  219
+    }
  220
+    /**
  221
+     * _processXml - Contains the logic for xml2json
  222
+     *
  223
+     * The logic in this function is a recursive one.
  224
+     *
  225
+     * The main caller of this function (i.e. fromXml) needs to provide
  226
+     * only the first two parameters i.e. the SimpleXMLElement object and
  227
+     * the flag for ignoring or not ignoring XML attributes. The third parameter
  228
+     * will be used internally within this function during the recursive calls.
  229
+     *
  230
+     * This function converts the SimpleXMLElement object into a PHP array by
  231
+     * calling a recursive (protected static) function in this class. Once all
  232
+     * the XML elements are stored in the PHP array, it is returned to the caller.
  233
+     *
  234
+     * Throws a Zend\Json\RecursionException if the XML tree is deeper than the allowed limit.
  235
+     *
  236
+     * @param SimpleXMLElement $simpleXmlElementObject
  237
+     * @param boolean $ignoreXmlAttributes
  238
+     * @param integer $recursionDepth
  239
+     * @return array
  240
+     */
  241
+    protected static function _processXml ($simpleXmlElementObject, $ignoreXmlAttributes, $recursionDepth=0) {
  242
+        // Keep an eye on how deeply we are involved in recursion.
  243
+        if ($recursionDepth > self::$maxRecursionDepthAllowed) {
  244
+            // XML tree is too deep. Exit now by throwing an exception.
  245
+            throw new RecursionException(
  246
+                "Function _processXml exceeded the allowed recursion depth of " .
  247
+                self::$maxRecursionDepthAllowed);
  248
+        } // End of if ($recursionDepth > self::$maxRecursionDepthAllowed)
  249
+        $childrens= $simpleXmlElementObject->children();
  250
+        $name= $simpleXmlElementObject->getName();
  251
+        $value= self::_getXmlValue($simpleXmlElementObject);
  252
+        $attributes= (array) $simpleXmlElementObject->attributes();
  253
+        if (count($childrens)==0) {
  254
+            if (!empty($attributes) && !$ignoreXmlAttributes) {
  255
+                foreach ($attributes['@attributes'] as $k => $v) {
  256
+                    $attributes['@attributes'][$k]= self::_getXmlValue($v);
  257
+                }
  258
+                if (!empty($value)) {
  259
+                    $attributes['@text']= $value;
  260
+                } 
  261
+                return array($name => $attributes);
  262
+            } else {
  263
+               return array($name => $value);
  264
+            }
  265
+        } else {
  266
+            $childArray= array();
  267
+            foreach ($childrens as $child) {
  268
+                $childname= $child->getName();
  269
+                $element= self::_processXml($child,$ignoreXmlAttributes,$recursionDepth+1);
  270
+                if (array_key_exists($childname, $childArray)) {
  271
+                    if (empty($subChild[$childname])) {
  272
+                        $childArray[$childname]=array($childArray[$childname]);
  273
+                        $subChild[$childname]=true;
  274
+                    }
  275
+                    $childArray[$childname][]= $element[$childname];
  276
+                } else {
  277
+                    $childArray[$childname]= $element[$childname];
  278
+                }
  279
+            }
  280
+            if (!empty($attributes) && !$ignoreXmlAttributes) {
  281
+                foreach ($attributes['@attributes'] as $k => $v) {
  282
+                    $attributes['@attributes'][$k]= self::_getXmlValue($v);
  283
+                }
  284
+                $childArray['@attributes']= $attributes['@attributes'];
  285
+            }
  286
+            if (!empty($value)) {
  287
+                $childArray['@text']= $value;
  288
+            }
  289
+            return array($name => $childArray);
  290
+        }
  291
+    }
199 292
 
200 293
     /**
201 294
      * fromXml - Converts XML to JSON
@@ -243,110 +336,6 @@ public static function fromXml ($xmlStringContents, $ignoreXmlAttributes=true) {
243 336
     }
244 337
 
245 338
     /**
246  
-     * _processXml - Contains the logic for xml2json
247  
-     *
248  
-     * The logic in this function is a recursive one.
249  
-     *
250  
-     * The main caller of this function (i.e. fromXml) needs to provide
251  
-     * only the first two parameters i.e. the SimpleXMLElement object and
252  
-     * the flag for ignoring or not ignoring XML attributes. The third parameter
253  
-     * will be used internally within this function during the recursive calls.
254  
-     *
255  
-     * This function converts the SimpleXMLElement object into a PHP array by
256  
-     * calling a recursive (protected static) function in this class. Once all
257  
-     * the XML elements are stored in the PHP array, it is returned to the caller.
258  
-     *
259  
-     * @static
260  
-     * @access protected
261  
-     * @param SimpleXMLElement $simpleXmlElementObject XML element to be converted
262  
-     * @param boolean $ignoreXmlAttributes Include or exclude XML attributes in
263  
-     * the xml2json conversion process.
264  
-     * @param int $recursionDepth Current recursion depth of this function
265  
-     * @return mixed - On success, a PHP associative array of traversed XML elements
266  
-     * @throws Zend\Json\Exception\RecursionException if the XML tree is deeper than the allowed limit
267  
-     */
268  
-    protected static function _processXml ($simpleXmlElementObject, $ignoreXmlAttributes, $recursionDepth=0) {
269  
-        // Keep an eye on how deeply we are involved in recursion.
270  
-        if ($recursionDepth > self::$maxRecursionDepthAllowed) {
271  
-            // XML tree is too deep. Exit now by throwing an exception.
272  
-            throw new RecursionException(
273  
-                "Function _processXml exceeded the allowed recursion depth of " .
274  
-                self::$maxRecursionDepthAllowed);
275  
-        } // End of if ($recursionDepth > self::$maxRecursionDepthAllowed)
276  
-
277  
-        if ($recursionDepth == 0) {
278  
-            // Store the original SimpleXmlElementObject sent by the caller.
279  
-            // We will need it at the very end when we return from here for good.
280  
-            $callerProvidedSimpleXmlElementObject = $simpleXmlElementObject;
281  
-        } // End of if ($recursionDepth == 0)
282  
-
283  
-        if ($simpleXmlElementObject instanceof \SimpleXMLElement) {
284  
-            // Get a copy of the simpleXmlElementObject
285  
-            $copyOfSimpleXmlElementObject = $simpleXmlElementObject;
286  
-            // Get the object variables in the SimpleXmlElement object for us to iterate.
287  
-            $simpleXmlElementObject = get_object_vars($simpleXmlElementObject);
288  
-        } // End of if (get_class($simpleXmlElementObject) == "SimpleXMLElement")
289  
-
290  
-        // It needs to be an array of object variables.
291  
-        if (is_array($simpleXmlElementObject)) {
292  
-            // Initialize a result array.
293  
-            $resultArray = array();
294  
-            // Is the input array size 0? Then, we reached the rare CDATA text if any.
295  
-            if (count($simpleXmlElementObject) <= 0) {
296  
-                // Let us return the lonely CDATA. It could even be
297  
-                // an empty element or just filled with whitespaces.
298  
-                return (trim(strval($copyOfSimpleXmlElementObject)));
299  
-            } // End of if (count($simpleXmlElementObject) <= 0)
300  
-
301  
-            // Let us walk through the child elements now.
302  
-            foreach($simpleXmlElementObject as $key=>$value) {
303  
-                // Check if we need to ignore the XML attributes.
304  
-                // If yes, you can skip processing the XML attributes.
305  
-                // Otherwise, add the XML attributes to the result array.
306  
-                if(($ignoreXmlAttributes == true) && (is_string($key)) && ($key == "@attributes")) {
307  
-                    continue;
308  
-                } // End of if(($ignoreXmlAttributes == true) && ($key == "@attributes"))
309  
-
310  
-                // Let us recursively process the current XML element we just visited.
311  
-                // Increase the recursion depth by one.
312  
-                $recursionDepth++;
313  
-                $resultArray[$key] = self::_processXml ($value, $ignoreXmlAttributes, $recursionDepth);
314  
-
315  
-                // Decrease the recursion depth by one.
316  
-                $recursionDepth--;
317  
-            } // End of foreach($simpleXmlElementObject as $key=>$value) {
318  
-
319  
-            if ($recursionDepth == 0) {
320  
-                // That is it. We are heading to the exit now.
321  
-                // Set the XML root element name as the root [top-level] key of
322  
-                // the associative array that we are going to return to the original
323  
-                // caller of this recursive function.
324  
-                $tempArray = $resultArray;
325  
-                $resultArray = array();
326  
-                $resultArray[$callerProvidedSimpleXmlElementObject->getName()] = $tempArray;
327  
-            } // End of if ($recursionDepth == 0)
328  
-
329  
-            return($resultArray);
330  
-        } else {
331  
-            // We are now looking at either the XML attribute text or
332  
-            // the text between the XML tags.
333  
-
334  
-            // In order to allow Zend_Json_Expr from xml, we check if the node
335  
-            // matchs the pattern that try to detect if it is a new Zend_Json_Expr
336  
-            // if it matches, we return a new Zend_Json_Expr instead of a text node
337  
-            $pattern = '/^[\s]*new Zend_Json_Expr[\s]*\([\s]*[\"\']{1}(.*)[\"\']{1}[\s]*\)[\s]*$/';
338  
-            $matchings = array();
339  
-            $match = preg_match($pattern, $simpleXmlElementObject, $matchings);
340  
-            if ($match) {
341  
-                return new Expr($matchings[1]);
342  
-            } else {
343  
-                return (trim(strval($simpleXmlElementObject)));
344  
-            }
345  
-
346  
-        } // End of if (is_array($simpleXmlElementObject))
347  
-    } // End of function _processXml.
348  
-    
349  
-    /**
350 339
      * Pretty-print JSON string
351 340
      * 
352 341
      * Use 'indent' option to select indentation string - by default it's a tab
112  tests/Zend/Json/JsonXmlTest.php
@@ -508,6 +508,118 @@ public function testUsingXML7()
508 508
     }
509 509
 */
510 510
 
  511
+    /**
  512
+     *  @group ZF-3257
  513
+     */
  514
+    public function testUsingXML8() {
  515
+
  516
+        // Set the XML contents that will be tested here.
  517
+        $xmlStringContents = <<<EOT
  518
+<?xml version="1.0"?>
  519
+<a><b id="foo" />bar</a>
  520
+
  521
+EOT;
  522
+
  523
+        // There are not going to be any XML attributes in this test XML.
  524
+        // Hence, set the flag to ignore XML attributes.
  525
+        $ignoreXmlAttributes = false;
  526
+        $jsonContents = "";
  527
+        $ex = null;
  528
+
  529
+        // Convert XML to JSON now.
  530
+        // fromXml function simply takes a String containing XML contents as input.
  531
+        try {
  532
+            $jsonContents = Json\Json::fromXml($xmlStringContents, $ignoreXmlAttributes);
  533
+        } catch (Exception $ex) {
  534
+            ;
  535
+        }
  536
+        $this->assertSame($ex, null, "Zend_JSON::fromXml returned an exception.");
  537
+
  538
+        // Convert the JSON string into a PHP array.
  539
+        $phpArray = Json\Json::decode($jsonContents, Json\Json::TYPE_ARRAY);
  540
+        // Test if it is not a NULL object.
  541
+        $this->assertNotNull($phpArray, "JSON result for XML input 1 is NULL");
  542
+
  543
+        $this->assertSame("bar", $phpArray['a']['@text'], "The text element of a is not correct");
  544
+        $this->assertSame("foo", $phpArray['a']['b']['@attributes']['id'], "The id attribute of b is not correct");
  545
+
  546
+    }
  547
+
  548
+    /**
  549
+     * @group ZF-11385
  550
+     * @expectedException Zend\Json\Exception\RecursionException
  551
+     * @dataProvider providerNestingDepthIsHandledProperly
  552
+     */
  553
+    public function testNestingDepthIsHandledProperlyWhenNestingDepthExceedsMaximum($xmlStringContents)
  554
+    {        
  555
+        Json\Json::$maxRecursionDepthAllowed = 1;
  556
+        Json\Json::fromXml($xmlStringContents, true);
  557
+    }
  558
+    
  559
+    /**
  560
+     * @group ZF-11385
  561
+     * @dataProvider providerNestingDepthIsHandledProperly
  562
+     */
  563
+    public function testNestingDepthIsHandledProperlyWhenNestingDepthDoesNotExceedMaximum($xmlStringContents)
  564
+    {   
  565
+        try {
  566
+            Json\Json::$maxRecursionDepthAllowed = 25;
  567
+            $jsonString = Json\Json::fromXml($xmlStringContents, true);
  568
+            $jsonArray = Json\Json::decode($jsonString, Json\Json::TYPE_ARRAY);
  569
+            $this->assertNotNull($jsonArray, "JSON decode result is NULL");
  570
+            $this->assertSame('A', $jsonArray['response']['message_type']['defaults']['close_rules']['after_responses']);
  571
+        } catch ( Zend\Json\Exception\RecursionException $ex ) {
  572
+            $this->fail('Zend_Json::fromXml does not implement recursion check properly');
  573
+        }
  574
+    }
  575
+    
  576
+    /**
  577
+     * XML document provider for ZF-11385 tests
  578
+     * @return array
  579
+     */
  580
+    public static function providerNestingDepthIsHandledProperly()
  581
+    {
  582
+        $xmlStringContents = <<<EOT
  583
+<response>
  584
+	<status>success</status>
  585
+	<description>200 OK</description>
  586
+	<message_type>
  587
+		<system_name>A</system_name>
  588
+		<shortname>B</shortname>
  589
+		<long_name>C</long_name>
  590
+		<as_verb>D</as_verb>
  591
+		<as_noun>E</as_noun>
  592
+		<challenge_phrase>F</challenge_phrase>
  593
+		<recipient_details>G</recipient_details>
  594
+		<sender_details>H</sender_details>
  595
+		<example_text>A</example_text>
  596
+		<short_description>B</short_description>
  597
+		<long_description>C</long_description>
  598
+		<version>D</version>
  599
+		<developer>E</developer>
  600
+		<config_instructions>A</config_instructions>
  601
+		<config_fragment>B</config_fragment>
  602
+		<icon_small>C</icon_small>
  603
+		<icon_medium>D</icon_medium>
  604
+		<icon_large>E</icon_large>
  605
+		<defaults>
  606
+			<close_rules>
  607
+				<after_responses>A</after_responses>
  608
+			</close_rules>
  609
+			<recipient_visibility>B</recipient_visibility>
  610
+			<recipient_invite>C</recipient_invite>
  611
+			<results_visibility>A</results_visibility>
  612
+			<response_visibility>B</response_visibility>
  613
+			<recipient_resubmit>C</recipient_resubmit>
  614
+			<feed_status>D</feed_status>
  615
+		</defaults>
  616
+	</message_type>
  617
+	<execution_time>0.0790269374847</execution_time>	
  618
+</response>
  619
+EOT;
  620
+        return array(array($xmlStringContents));
  621
+    }
  622
+
511 623
 }
512 624
 
513 625
 
Commit_comment_tip

Tip: You can add notes to lines in a file. Hover to the left of a line to make a note

Something went wrong with that request. Please try again.