MediaWiki  REL1_23
FormatJson.php
Go to the documentation of this file.
00001 <?php
00026 class FormatJson {
00034     const UTF8_OK = 1;
00035 
00046     const XMLMETA_OK = 2;
00047 
00055     const ALL_OK = 3;
00056 
00066     const WS_CLEANUP_REGEX = '/(?<=[\[{])\n\s*+(?=[\]}])/';
00067 
00074     private static $badChars = array(
00075         "\xe2\x80\xa8", // U+2028 LINE SEPARATOR
00076         "\xe2\x80\xa9", // U+2029 PARAGRAPH SEPARATOR
00077     );
00078 
00082     private static $badCharsEscaped = array(
00083         '\u2028', // U+2028 LINE SEPARATOR
00084         '\u2029', // U+2029 PARAGRAPH SEPARATOR
00085     );
00086 
00104     public static function encode( $value, $pretty = false, $escaping = 0 ) {
00105         if ( !is_string( $pretty ) ) {
00106             $pretty = $pretty ? '    ' : false;
00107         }
00108 
00109         if ( defined( 'JSON_UNESCAPED_UNICODE' ) ) {
00110             return self::encode54( $value, $pretty, $escaping );
00111         }
00112 
00113         return self::encode53( $value, $pretty, $escaping );
00114     }
00115 
00126     public static function decode( $value, $assoc = false ) {
00127         return json_decode( $value, $assoc );
00128     }
00129 
00138     private static function encode54( $value, $pretty, $escaping ) {
00139         // PHP escapes '/' to prevent breaking out of inline script blocks using '</script>',
00140         // which is hardly useful when '<' and '>' are escaped (and inadequate), and such
00141         // escaping negatively impacts the human readability of URLs and similar strings.
00142         $options = JSON_UNESCAPED_SLASHES;
00143         $options |= $pretty !== false ? JSON_PRETTY_PRINT : 0;
00144         $options |= ( $escaping & self::UTF8_OK ) ? JSON_UNESCAPED_UNICODE : 0;
00145         $options |= ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
00146         $json = json_encode( $value, $options );
00147         if ( $json === false ) {
00148             return false;
00149         }
00150 
00151         if ( $pretty !== false ) {
00152             // Remove whitespace inside empty arrays/objects; different JSON encoders
00153             // vary on this, and we want our output to be consistent across implementations.
00154             $json = preg_replace( self::WS_CLEANUP_REGEX, '', $json );
00155             if ( $pretty !== '    ' ) {
00156                 // Change the four-space indent to a tab indent
00157                 $json = str_replace( "\n    ", "\n\t", $json );
00158                 while ( strpos( $json, "\t    " ) !== false ) {
00159                     $json = str_replace( "\t    ", "\t\t", $json );
00160                 }
00161 
00162                 if ( $pretty !== "\t" ) {
00163                     // Change the tab indent to the provided indent
00164                     $json = str_replace( "\t", $pretty, $json );
00165                 }
00166             }
00167         }
00168         if ( $escaping & self::UTF8_OK ) {
00169             $json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
00170         }
00171 
00172         return $json;
00173     }
00174 
00184     private static function encode53( $value, $pretty, $escaping ) {
00185         $options = ( $escaping & self::XMLMETA_OK ) ? 0 : ( JSON_HEX_TAG | JSON_HEX_AMP );
00186         $json = json_encode( $value, $options );
00187         if ( $json === false ) {
00188             return false;
00189         }
00190 
00191         // Emulate JSON_UNESCAPED_SLASHES. Because the JSON contains no unescaped slashes
00192         // (only escaped slashes), a simple string replacement works fine.
00193         $json = str_replace( '\/', '/', $json );
00194 
00195         if ( $escaping & self::UTF8_OK ) {
00196             // JSON hex escape sequences follow the format \uDDDD, where DDDD is four hex digits
00197             // indicating the equivalent UTF-16 code unit's value. To most efficiently unescape
00198             // them, we exploit the JSON extension's built-in decoder.
00199             // * We escape the input a second time, so any such sequence becomes \\uDDDD.
00200             // * To avoid interpreting escape sequences that were in the original input,
00201             //   each double-escaped backslash (\\\\) is replaced with \\\u005c.
00202             // * We strip one of the backslashes from each of the escape sequences to unescape.
00203             // * Then the JSON decoder can perform the actual unescaping.
00204             $json = str_replace( "\\\\\\\\", "\\\\\\u005c", addcslashes( $json, '\"' ) );
00205             $json = json_decode( preg_replace( "/\\\\\\\\u(?!00[0-7])/", "\\\\u", "\"$json\"" ) );
00206             $json = str_replace( self::$badChars, self::$badCharsEscaped, $json );
00207         }
00208 
00209         if ( $pretty !== false ) {
00210             return self::prettyPrint( $json, $pretty );
00211         }
00212 
00213         return $json;
00214     }
00215 
00224     private static function prettyPrint( $json, $indentString ) {
00225         $buf = '';
00226         $indent = 0;
00227         $json = strtr( $json, array( '\\\\' => '\\\\', '\"' => "\x01" ) );
00228         for ( $i = 0, $n = strlen( $json ); $i < $n; $i += $skip ) {
00229             $skip = 1;
00230             switch ( $json[$i] ) {
00231                 case ':':
00232                     $buf .= ': ';
00233                     break;
00234                 case '[':
00235                 case '{':
00236                     ++$indent;
00237                     // falls through
00238                 case ',':
00239                     $buf .= $json[$i] . "\n" . str_repeat( $indentString, $indent );
00240                     break;
00241                 case ']':
00242                 case '}':
00243                     $buf .= "\n" . str_repeat( $indentString, --$indent ) . $json[$i];
00244                     break;
00245                 case '"':
00246                     $skip = strcspn( $json, '"', $i + 1 ) + 2;
00247                     $buf .= substr( $json, $i, $skip );
00248                     break;
00249                 default:
00250                     $skip = strcspn( $json, ',]}"', $i + 1 ) + 1;
00251                     $buf .= substr( $json, $i, $skip );
00252             }
00253         }
00254         $buf = preg_replace( self::WS_CLEANUP_REGEX, '', $buf );
00255 
00256         return str_replace( "\x01", '\"', $buf );
00257     }
00258 }