diff options
Diffstat (limited to 'includes/json/FormatJson.php')
-rw-r--r-- | includes/json/FormatJson.php | 102 |
1 files changed, 95 insertions, 7 deletions
diff --git a/includes/json/FormatJson.php b/includes/json/FormatJson.php index f3e5c76d..095811ff 100644 --- a/includes/json/FormatJson.php +++ b/includes/json/FormatJson.php @@ -71,6 +71,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. * * This doesn't affect regular strings inside the JSON because those can't @@ -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 ); + } } |