Merge "Mock error event firing in mw.loader test"
[lhc/web/wiklou.git] / includes / json / FormatJson.php
index f3e5c76..095811f 100644 (file)
@@ -70,6 +70,13 @@ class FormatJson {
         */
        const TRY_FIXING = 0x200;
 
+       /**
+        * If set, strip comments from input before parsing as JSON.
+        *
+        * @since 1.25
+        */
+       const STRIP_COMMENTS = 0x400;
+
        /**
         * Regex that matches whitespace inside empty arrays and objects.
         *
@@ -115,7 +122,7 @@ class FormatJson {
         *   readability, using that string for indentation. If true, use the default indent
         *   string (four spaces).
         * @param int $escaping Bitfield consisting of _OK class constants
-        * @return string|bool: String if successful; false upon failure
+        * @return string|false String if successful; false upon failure
         */
        public static function encode( $value, $pretty = false, $escaping = 0 ) {
                if ( !is_string( $pretty ) ) {
@@ -130,8 +137,9 @@ class FormatJson {
        }
 
        /**
-        * Decodes a JSON string. It is recommended to use FormatJson::parse(), which returns more comprehensive
-        * result in case of an error, and has more parsing options.
+        * Decodes a JSON string. It is recommended to use FormatJson::parse(),
+        * which returns more comprehensive result in case of an error, and has
+        * more parsing options.
         *
         * @param string $value The JSON string being decoded
         * @param bool $assoc When true, returned objects will be converted into associative arrays.
@@ -147,13 +155,18 @@ class FormatJson {
 
        /**
         * Decodes a JSON string.
-        * Unlike FormatJson::decode(), if $value represents null value, it will be properly decoded as valid.
+        * Unlike FormatJson::decode(), if $value represents null value, it will be
+        * properly decoded as valid.
         *
         * @param string $value The JSON string being decoded
-        * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING
+        * @param int $options A bit field that allows FORCE_ASSOC, TRY_FIXING,
+        * STRIP_COMMENTS
         * @return Status If valid JSON, the value is available in $result->getValue()
         */
        public static function parse( $value, $options = 0 ) {
+               if ( $options & self::STRIP_COMMENTS ) {
+                       $value = self::stripComments( $value );
+               }
                $assoc = ( $options & self::FORCE_ASSOC ) !== 0;
                $result = json_decode( $value, $assoc );
                $code = json_last_error();
@@ -219,7 +232,7 @@ class FormatJson {
         * @param mixed $value
         * @param string|bool $pretty
         * @param int $escaping
-        * @return string|bool
+        * @return string|false
         */
        private static function encode54( $value, $pretty, $escaping ) {
                static $bug66021;
@@ -271,7 +284,7 @@ class FormatJson {
         * @param mixed $value
         * @param string|bool $pretty
         * @param int $escaping
-        * @return string|bool
+        * @return string|false
         */
        private static function encode53( $value, $pretty, $escaping ) {
                $options = ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
@@ -347,4 +360,79 @@ class FormatJson {
 
                return str_replace( "\x01", '\"', $buf );
        }
+
+       /**
+        * Remove multiline and single line comments from an otherwise valid JSON
+        * input string. This can be used as a preprocessor for to allow JSON
+        * formatted configuration files to contain comments.
+        *
+        * @param string $json
+        * @return string JSON with comments removed
+        */
+       public static function stripComments( $json ) {
+               // Ensure we have a string
+               $str = (string) $json;
+               $buffer = '';
+               $maxLen = strlen( $str );
+               $mark = 0;
+
+               $inString = false;
+               $inComment = false;
+               $multiline = false;
+
+               for ($idx = 0; $idx < $maxLen; $idx++) {
+                       switch ( $str[$idx] ) {
+                               case '"':
+                                       $lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : '';
+                                       if ( !$inComment && $lookBehind !== '\\' ) {
+                                               // Either started or ended a string
+                                               $inString = !$inString;
+                                       }
+                                       break;
+
+                               case '/':
+                                       $lookAhead = ( $idx + 1 < $maxLen ) ? $str[$idx + 1] : '';
+                                       $lookBehind = ( $idx - 1 >= 0 ) ? $str[$idx - 1] : '';
+                                       if ( $inString ) {
+                                               continue;
+
+                                       } elseif ( !$inComment &&
+                                               ( $lookAhead === '/' || $lookAhead === '*' )
+                                       ) {
+                                               // Transition into a comment
+                                               // Add characters seen to buffer
+                                               $buffer .= substr( $str, $mark, $idx - $mark );
+                                               // Consume the look ahead character
+                                               $idx++;
+                                               // Track state
+                                               $inComment = true;
+                                               $multiline = $lookAhead === '*';
+
+                                       } elseif ( $multiline && $lookBehind === '*' ) {
+                                               // Found the end of the current comment
+                                               $mark = $idx + 1;
+                                               $inComment = false;
+                                               $multiline = false;
+                                       }
+                                       break;
+
+                               case "\n":
+                                       if ( $inComment && !$multiline ) {
+                                               // Found the end of the current comment
+                                               $mark = $idx + 1;
+                                               $inComment = false;
+                                       }
+                                       break;
+                       }
+               }
+               if ( $inComment ) {
+                       // Comment ends with input
+                       // Technically we should check to ensure that we aren't in
+                       // a multiline comment that hasn't been properly ended, but this
+                       // is a strip filter, not a validating parser.
+                       $mark = $maxLen;
+               }
+               // Add final chunk to buffer before returning
+               return $buffer . substr( $str, $mark, $maxLen - $mark );
+       }
 }