array(), 'body' => $raw, 'errors' => array(), ); if ( ! preg_match( '/\A---[ \t]*\n(.*?)\n---[ \t]*(?:\n|\z)(.*)\z/s', $raw, $m ) ) { $out['errors'][] = 'no_front_matter'; return $out; } $out['body'] = $m[2]; $list_key = null; foreach ( explode( "\n", $m[1] ) as $line ) { $trim = trim( $line ); if ( '' === $trim || '#' === $trim[0] ) { continue; } if ( preg_match( '/^([A-Za-z_][A-Za-z0-9_-]*)[ \t]*:(?:[ \t]+(.*))?$/', $line, $kv ) ) { $key = strtolower( str_replace( '-', '_', $kv[1] ) ); $val = isset( $kv[2] ) ? trim( $kv[2] ) : ''; if ( '' === $val ) { $out['meta'][ $key ] = ''; $list_key = $key; } else { $out['meta'][ $key ] = self::value( $val ); $list_key = null; } } elseif ( null !== $list_key && preg_match( '/^[ \t]*-[ \t]+(.*)$/', $line, $li ) ) { if ( ! is_array( $out['meta'][ $list_key ] ) ) { $out['meta'][ $list_key ] = array(); } $out['meta'][ $list_key ][] = self::unquote( trim( $li[1] ) ); } else { $out['errors'][] = 'bad_line'; } } return $out; } /** * Parse a scalar or an inline list. * * @param string $val Raw value. * @return string|string[] */ private static function value( $val ) { if ( strlen( $val ) >= 2 && '[' === $val[0] && ']' === substr( $val, -1 ) ) { return self::split_list( substr( $val, 1, -1 ) ); } return self::unquote( $val ); } /** * Split "a, 'b, c', d" into items, respecting quotes. * * @param string $inner List contents without brackets. * @return string[] */ private static function split_list( $inner ) { $items = array(); $cur = ''; $quote = ''; $len = strlen( $inner ); for ( $i = 0; $i < $len; $i++ ) { $ch = $inner[ $i ]; if ( '' !== $quote ) { if ( '\\' === $ch && '"' === $quote && $i + 1 < $len ) { $cur .= $ch . $inner[ ++$i ]; continue; } if ( $ch === $quote ) { $quote = ''; } $cur .= $ch; continue; } // A quote only opens a quoted item at the start of the item (so "driver's" is safe). if ( ( '"' === $ch || "'" === $ch ) && '' === trim( $cur ) ) { $quote = $ch; $cur .= $ch; continue; } if ( ',' === $ch ) { $items[] = $cur; $cur = ''; continue; } $cur .= $ch; } $items[] = $cur; $out = array(); foreach ( $items as $item ) { $item = self::unquote( trim( $item ) ); if ( '' !== $item ) { $out[] = $item; } } return $out; } /** * Remove matching surrounding quotes. * * @param string $s Value. * @return string */ private static function unquote( $s ) { $n = strlen( $s ); if ( $n >= 2 ) { $first = $s[0]; $last = $s[ $n - 1 ]; if ( '"' === $first && '"' === $last ) { return str_replace( array( '\\"', '\\\\' ), array( '"', '\\' ), substr( $s, 1, -1 ) ); } if ( "'" === $first && "'" === $last ) { return str_replace( "''", "'", substr( $s, 1, -1 ) ); } } return $s; } }