Changeset 2501
- Timestamp:
- 02/15/2016 06:28:02 AM (9 years ago)
- Location:
- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
- Files:
-
- 3 added
- 4 edited
Legend:
- Unmodified
- Added
- Removed
-
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/plugin-directory.php
r2499 r2501 1 1 <?php 2 /* 3 * Plugin Name: Plugin Repository2 /** 3 * Plugin Name: Plugin Directory 4 4 * Plugin URI: http://wordpress.org/plugins/ 5 5 * Description: Transforms a WordPress site in The Official Plugin Directory. 6 6 * Version: 0.1 7 * Author: wordpressdotorg8 * Author URI: http ://wordpress.org/7 * Author: the WordPress team 8 * Author URI: https://wordpress.org/ 9 9 * Text Domain: wporg-plugins 10 10 * License: GPLv2 11 11 * License URI: http://opensource.org/licenses/gpl-2.0.php 12 * 13 * @package WPorg_Plugin_Directory 12 14 */ 13 15 14 class Plugin_Directory { 16 include_once( 'class-wporg-plugin-directory.php' ); 17 include_once( 'class-wporg-plugin-directory-template.php' ); 18 include_once( 'class-wporg-plugin-directory-tools.php' ); 15 19 16 function __construct() { 17 register_activation_hook( __FILE__, array( $this, 'activate' ) ); 18 register_deactivation_hook( __FILE__, array( $this, 'deactivate' ) ); 19 20 add_action( 'init', array( $this, 'init' ) ); 21 add_filter( 'post_type_link', array( $this, 'package_link' ), 10, 2 ); 22 add_filter( 'pre_insert_term', array( $this, 'pre_insert_term_prevent' ) ); 23 add_action( 'pre_get_posts', array( $this, 'use_plugins_in_query' ) ); 24 add_filter( 'the_content', array( $this, 'filter_post_content_to_correct_page' ), 1 ); 25 } 26 27 function activate() { 28 global $wp_rewrite; 29 30 // Setup the environment 31 $this->init(); 32 33 // %postname% is required 34 $wp_rewrite->set_permalink_structure( '/%postname%/' ); 35 36 // /tags/%slug% is required for tags 37 $wp_rewrite->set_tag_base( '/tags' ); 38 39 // We require the WordPress.org Ratings plugin also be active 40 if ( ! is_plugin_active( 'wporg-ratings/wporg-ratings.php' ) ) { 41 activate_plugin( 'wporg-ratings/wporg-ratings.php' ); 42 } 43 44 // Enable the WordPress.org Theme Repo Theme 45 foreach ( wp_get_themes() as $theme ) { 46 if ( $theme->get( 'Name' ) === 'WordPress.org Plugins' ) { 47 switch_theme( $theme->get_stylesheet() ); 48 break; 49 } 50 } 51 52 flush_rewrite_rules(); 53 54 do_action( 'wporg_plugins_activation' ); 55 } 56 57 function deactivate() { 58 flush_rewrite_rules(); 59 60 do_action( 'wporg_plugins_deactivation' ); 61 } 62 63 64 function init() { 65 load_plugin_textdomain( 'wporg-plugins' ); 66 67 register_post_type( 'plugin', array( 68 'labels' => array( 69 'name' => __( 'Plugins', 'wporg-plugins' ), 70 'singular_name' => __( 'Plugin', 'wporg-plugins' ), 71 'add_new' => __( 'Add New', 'wporg-plugins' ), 72 'add_new_item' => __( 'Add New Plugin', 'wporg-plugins' ), 73 'edit_item' => __( 'Edit Plugin', 'wporg-plugins' ), 74 'new_item' => __( 'New Plugin', 'wporg-plugins' ), 75 'view_item' => __( 'View Plugin', 'wporg-plugins' ), 76 'search_items' => __( 'Search Plugins', 'wporg-plugins' ), 77 'not_found' => __( 'No plugins found', 'wporg-plugins' ), 78 'not_found_in_trash' => __( 'No plugins found in Trash', 'wporg-plugins' ), 79 'menu_name' => __( 'My Plugins', 'wporg-plugins' ), 80 ), 81 'description' => __( 'A package', 'wporg-plugins' ), 82 'supports' => array( 'title', 'editor', 'excerpt', 'custom-fields' ), 83 'taxonomies' => array( 'post_tag', 'category' ), 84 'public' => true, 85 'show_ui' => true, 86 'has_archive' => true, 87 'rewrite' => false, 88 'menu_icon' => 'dashicons-admin-plugins', 89 ) ); 90 91 register_post_status( 'pending', array( 92 'label' => _x( 'Pending', 'plugin status', 'wporg-plugins' ), 93 'public' => false, 94 'show_in_admin_status_list' => true, 95 'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>', 'wporg-plugins' ), 96 ) ); 97 register_post_status( 'disabled', array( 98 'label' => _x( 'Disabled', 'plugin status', 'wporg-plugins' ), 99 'public' => false, 100 'show_in_admin_status_list' => true, 101 'label_count' => _n_noop( 'Disabled <span class="count">(%s)</span>', 'Disabled <span class="count">(%s)</span>', 'wporg-plugins' ), 102 ) ); 103 register_post_status( 'closed', array( 104 'label' => _x( 'Closed', 'plugin status', 'wporg-plugins' ), 105 'public' => false, 106 'show_in_admin_status_list' => true, 107 'label_count' => _n_noop( 'Closed <span class="count">(%s)</span>', 'Closed <span class="count">(%s)</span>', 'wporg-plugins' ), 108 ) ); 109 110 // Add the browse/* views 111 add_rewrite_tag( '%browse%', '(featured|popular|beta|new|favorites)' ); 112 add_permastruct( 'browse', 'browse/%browse%' ); 113 114 add_rewrite_endpoint( 'installation', EP_PERMALINK ); 115 add_rewrite_endpoint( 'faq', EP_PERMALINK ); 116 add_rewrite_endpoint( 'screenshots', EP_PERMALINK ); 117 add_rewrite_endpoint( 'changelog', EP_PERMALINK ); 118 add_rewrite_endpoint( 'stats', EP_PERMALINK ); 119 add_rewrite_endpoint( 'developers', EP_PERMALINK ); 120 add_rewrite_endpoint( 'other_notes', EP_PERMALINK ); 121 } 122 123 /** 124 * Filter the permalink for the Packages to be /post_name/ 125 * 126 * @param string $link The generated permalink 127 * @param string $post The package object 128 * @return string 129 */ 130 function package_link( $link, $post ) { 131 if ( 'plugin' != $post->post_type ) { 132 return $link; 133 } 134 135 return trailingslashit( home_url( $post->post_name ) ); 136 } 137 138 /** 139 * Checks if ther current users is a super admin before allowing terms to be added. 140 * 141 * @param string $term The term to add or update. 142 * @return string|WP_Error The term to add or update or WP_Error on failure. 143 */ 144 function pre_insert_term_prevent( $term ) { 145 if ( ! is_super_admin() ) { 146 $term = new WP_Error( 'not-allowed', __( 'You are not allowed to add terms.', 'wporg-plugins' ) ); 147 } 148 149 return $term; 150 } 151 152 function use_plugins_in_query( $wp_query ) { 153 if ( ! $wp_query->is_main_query() ) { 154 return; 155 } 156 157 if ( empty( $wp_query->query_vars['pagename'] ) && 158 ( empty( $wp_query->query_vars['post_type'] ) || 'posts' == $wp_query->query_vars['post_type'] ) ) { 159 $wp_query->query_vars['post_type'] = array( 'plugin' ); 160 } 161 162 if ( empty( $wp_query->query ) ) { 163 $wp_query->query_vars['browse'] = 'featured'; 164 } 165 166 switch ( get_query_var( 'browse' ) ) { 167 case 'beta': 168 $wp_query->query_vars['category_name'] = 'beta'; 169 break; 170 171 case 'featured': 172 $wp_query->query_vars['category_name'] = 'featured'; 173 break; 174 175 case 'favorites': 176 break; 177 178 case 'popular': 179 break; 180 } 181 182 // Re-route the Endpoints to the `content_page` query var. 183 if ( !empty( $wp_query->query['name'] ) ) { 184 foreach ( array( 'installation', 'faq', 'screenshots', 'changelog', 'stats', 'developers', 'other_notes' ) as $plugin_field ) { 185 if ( isset( $wp_query->query[ $plugin_field ] ) ) { 186 $wp_query->query['content_page'] = $wp_query->query_vars['content_page'] = $plugin_field; 187 unset( $wp_query->query[ $plugin_field ], $wp_query->query_vars[ $plugin_field ] ); 188 } 189 } 190 } 191 } 192 193 function filter_post_content_to_correct_page( $content ) { 194 global $content_pages; 195 196 $post = get_post(); 197 if ( 'plugin' != $post->post_type ) { 198 return $content; 199 } 200 201 $page = get_query_var( 'content_page' ); 202 $content_pages = $this->split_post_content_into_pages( $content ); 203 204 if ( ! isset( $content_pages[ $page ] ) ) { 205 $page = 'description'; 206 } 207 208 return $content_pages[ $page ]; 209 } 210 211 function split_post_content_into_pages( $content ) { 212 $content_pages = array( 213 'stats' => '[wporg-plugins-stats]', 214 'developers' => '[wporg-plugins-developer]', 215 ); 216 $_pages = preg_split( "#<!--section=(.+?)-->#", $content, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY ); 217 for ( $i = 0; $i < count( $_pages ); $i += 2 ) { 218 // Don't overwrite existing tabs. 219 if ( ! isset( $content_pages[ $_pages[ $i ] ] ) ) { 220 $content_pages[ $_pages[ $i ] ] = $_pages[ $i + 1 ]; 221 } 222 } 223 224 return $content_pages; 225 } 226 227 } 228 new Plugin_Directory(); 229 230 // Various functions used by other processes, will make sense to move to specific classses. 231 class Plugin_Directory_Tools { 232 static function get_readme_data( $readme ) { 233 // Uses https://github.com/rmccue/WordPress-Readme-Parser (with modifications) 234 include_once __DIR__ . '/readme-parser/markdown.php'; 235 include_once __DIR__ . '/readme-parser/compat.php'; 236 237 $data = (object) _WordPress_org_Readme::parse_readme( $readme ); 238 239 unset( $data->sections['screenshots'] ); // Useless 240 241 // sanitize contributors. 242 foreach ( $data->contributors as $i => $name ) { 243 if ( get_user_by( 'login', $name ) ) { 244 continue; 245 } elseif ( false !== ( $user = get_user_by( 'slug', $name ) ) ) { 246 $data->contributors[] = $user->user_login; 247 unset( $data->contributors[ $i ] ); 248 } else { 249 unset( $data->contributors[ $i ] ); 250 } 251 } 252 253 return $data; 254 } 255 } 256 257 // Various helpers to retrieve data not stored within WordPress 258 class Plugin_Directory_Template_Helpers { 259 static function get_active_installs_count( $plugin_slug ) { 260 global $wpdb; 261 262 $count = wp_cache_get( $plugin_slug, 'plugin_active_installs' ); 263 if ( false === $count ) { 264 $count = (int) $wpdb->get_var( $wpdb->prepare( 265 "SELECT count FROM rev2_daily_stat_summary WHERE type = 'plugin' AND type_name = %s AND stat = 'active_installs' LIMIT 1", 266 $plugin_slug 267 ) ); 268 wp_cache_add( $plugin_slug, $count, 'plugin_active_installs', 1200 ); 269 } 270 271 if ( $count < 10 ) { 272 return 0; 273 } 274 275 if ( $count >= 1000000 ) { 276 return 1000000; 277 } 278 279 return strval( $count )[0] * pow( 10, floor( log10( $count ) ) ); 280 } 281 282 static function get_total_downloads() { 283 global $wpdb; 284 285 $count = wp_cache_get( 'plugin_download_count', 'plugin_download_count' ); 286 if ( false === $count ) { 287 $count = (int) $wpdb->get_var( "SELECT SUM(downloads) FROM `plugin_2_stats`" ); 288 wp_cache_add( 'plugin_download_count', $count, 'plugin_download_count', DAY_IN_SECONDS ); 289 } 290 291 return $count; 292 } 293 294 static function get_plugin_sections() { 295 $plugin_slug = get_post()->post_name; 296 $raw_sections = get_post_meta( get_the_ID(), 'sections', true ); 297 $raw_sections = array_unique( array_merge( $raw_sections, array( 'description', 'stats', 'support', 'reviews', 'developers' ) ) ); 298 299 $sections = array(); 300 foreach ( $raw_sections as $section_slug ) { 301 $url = get_permalink(); 302 switch ( $section_slug ) { 303 case 'description': 304 $title = _x( 'Description', 'plugin tab title', 'wporg-plugins' ); 305 break; 306 case 'installation': 307 $title = _x( 'Installation', 'plugin tab title', 'wporg-plugins' ); 308 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 309 break; 310 case 'faq': 311 $title = _x( 'FAQ', 'plugin tab title', 'wporg-plugins' ); 312 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 313 break; 314 case 'screenshots': 315 $title = _x( 'Screenshots', 'plugin tab title', 'wporg-plugins' ); 316 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 317 break; 318 case 'changelog': 319 $title = _x( 'Changelog', 'plugin tab title', 'wporg-plugins' ); 320 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 321 break; 322 case 'stats': 323 $title = _x( 'Stats', 'plugin tab title', 'wporg-plugins' ); 324 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 325 break; 326 case 'support': 327 $title = _x( 'Support', 'plugin tab title', 'wporg-plugins' ); 328 $url = 'https://wordpress.org/support/plugin/' . $plugin_slug; 329 break; 330 case 'reviews': 331 $title = _x( 'Reviews', 'plugin tab title', 'wporg-plugins' ); 332 $url = 'https://wordpress.org/support/view/plugin-reviews/' . $plugin_slug; 333 break; 334 case 'developers': 335 $title = _x( 'Developers', 'plugin tab title', 'wporg-plugins' ); 336 $url = trailingslashit( $url ) . '/' . $section_slug . '/'; 337 break; 338 } 339 $sections[] = array( 340 'slug' => $section_slug, 341 'url' => $url, 342 'title' => $title, 343 ); 344 } 345 return $sections; 346 } 347 } 348 20 $wporg_plugin_directory = new WPorg_Plugin_Directory(); 21 register_activation_hook( __FILE__, array( $wporg_plugin_directory, 'activate' ) ); 22 register_deactivation_hook( __FILE__, array( $wporg_plugin_directory, 'deactivate' ) ); -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme-parser/ReadmeParser.php
r2499 r2501 1 1 <?php 2 2 /** 3 * Custom readme parser 3 * Custom readme parser. 4 4 * 5 5 * Based on Automattic_Readme from http://code.google.com/p/wordpress-plugin-readme-parser/ … … 10 10 * @todo Create validator for this based on http://code.google.com/p/wordpress-plugin-readme-parser/source/browse/trunk/validator.php 11 11 */ 12 13 /** 14 * Class Baikonur_ReadmeParser 15 */ 12 16 class Baikonur_ReadmeParser { 13 14 public static function parse_readme($file) {15 $contents = file($file); 16 return self::parse_readme_contents( $contents);17 } 18 19 public static function parse_readme_contents( $contents) {20 if ( is_string($contents)) {21 $contents = explode( "\n", $contents);17 public static function parse_readme( $file ) { 18 $contents = file( $file ); 19 20 return self::parse_readme_contents( $contents ); 21 } 22 23 public static function parse_readme_contents( $contents ) { 24 if ( is_string( $contents ) ) { 25 $contents = explode( "\n", $contents ); 22 26 } 23 27 24 28 $this_class = __CLASS__; 25 if ( function_exists('get_called_class')) {29 if ( function_exists( 'get_called_class' ) ) { 26 30 $this_class = get_called_class(); 27 31 } 28 32 29 $contents = array_map( array($this_class, 'strip_newlines'), $contents);33 $contents = array_map( array( $this_class, 'strip_newlines' ), $contents ); 30 34 31 35 // Strip BOM 32 if ( strpos($contents[0], "\xEF\xBB\xBF") === 0) {33 $contents[0] = substr( $contents[0], 3);36 if ( strpos( $contents[0], "\xEF\xBB\xBF" ) === 0 ) { 37 $contents[0] = substr( $contents[0], 3 ); 34 38 } 35 39 … … 37 41 38 42 // Defaults 39 $data->is_excerpt = false;40 $data->is_truncated = false;41 $data->tags = array();42 $data->requires = '';43 $data->tested = '';44 $data->contributors = array();45 $data->stable_tag = '';46 $data->version = '';47 $data->donate_link = '';43 $data->is_excerpt = false; 44 $data->is_truncated = false; 45 $data->tags = array(); 46 $data->requires = ''; 47 $data->tested = ''; 48 $data->contributors = array(); 49 $data->stable_tag = ''; 50 $data->version = ''; 51 $data->donate_link = ''; 48 52 $data->short_description = ''; 49 $data->sections = array();50 $data->changelog = array();51 $data->upgrade_notice = array();52 $data->screenshots = array();53 $data->sections = array(); 54 $data->changelog = array(); 55 $data->upgrade_notice = array(); 56 $data->screenshots = array(); 53 57 $data->remaining_content = array(); 54 58 55 $line = call_user_func_array(array($this_class, 'get_first_nonwhitespace'), array(&$contents));59 $line = call_user_func_array( array( $this_class, 'get_first_nonwhitespace' ), array( &$contents ) ); 56 60 $data->name = $line; 57 $data->name = trim( $data->name, "#= ");61 $data->name = trim( $data->name, "#= " ); 58 62 59 63 // Parse headers 60 64 $headers = array(); 61 65 62 $line = call_user_func_array( array($this_class, 'get_first_nonwhitespace'), array(&$contents));66 $line = call_user_func_array( array( $this_class, 'get_first_nonwhitespace' ), array( &$contents ) ); 63 67 do { 64 68 $key = $value = null; 65 if ( strpos($line, ':') === false) {69 if ( strpos( $line, ':' ) === false ) { 66 70 break; 67 71 } 68 $bits = explode(':', $line, 2); 69 list($key, $value) = $bits; 70 $key = strtolower(str_replace(array(' ', "\t"), '_', trim($key))); 71 if ($key === 'tags' && isset($headers['tags'])) { 72 $headers[$key] .= ',' . trim($value); 73 } 74 else { 75 $headers[$key] = trim($value); 76 } 77 } 78 while (($line = array_shift($contents)) !== null && ($line = trim($line)) && !empty($line)); 79 array_unshift($contents, $line); 80 81 if (!empty($headers['tags'])) { 82 $data->tags = explode(',', $headers['tags']); 83 $data->tags = array_map('trim', $data->tags); 84 } 85 if (!empty($headers['requires'])) { 72 $bits = explode( ':', $line, 2 ); 73 list( $key, $value ) = $bits; 74 $key = strtolower( str_replace( array( ' ', "\t" ), '_', trim( $key ) ) ); 75 if ( $key === 'tags' && isset( $headers['tags'] ) ) { 76 $headers[ $key ] .= ',' . trim( $value ); 77 } else { 78 $headers[ $key ] = trim( $value ); 79 } 80 } while ( ( $line = array_shift( $contents ) ) !== null && ( $line = trim( $line ) ) && ! empty( $line ) ); 81 array_unshift( $contents, $line ); 82 83 if ( ! empty( $headers['tags'] ) ) { 84 $data->tags = explode( ',', $headers['tags'] ); 85 $data->tags = array_map( 'trim', $data->tags ); 86 } 87 if ( ! empty( $headers['requires'] ) ) { 86 88 $data->requires = $headers['requires']; 87 89 } 88 if ( !empty($headers['requires_at_least'])) {90 if ( ! empty( $headers['requires_at_least'] ) ) { 89 91 $data->requires = $headers['requires_at_least']; 90 92 } 91 if ( !empty($headers['tested'])) {93 if ( ! empty( $headers['tested'] ) ) { 92 94 $data->tested = $headers['tested']; 93 95 } 94 if ( !empty($headers['tested_up_to'])) {96 if ( ! empty( $headers['tested_up_to'] ) ) { 95 97 $data->tested = $headers['tested_up_to']; 96 98 } 97 if ( !empty($headers['contributors'])) {98 $data->contributors = explode( ',', $headers['contributors']);99 $data->contributors = array_map( 'trim', $data->contributors);100 } 101 if ( !empty($headers['stable_tag'])) {99 if ( ! empty( $headers['contributors'] ) ) { 100 $data->contributors = explode( ',', $headers['contributors'] ); 101 $data->contributors = array_map( 'trim', $data->contributors ); 102 } 103 if ( ! empty( $headers['stable_tag'] ) ) { 102 104 $data->stable_tag = $headers['stable_tag']; 103 105 } 104 if ( !empty($headers['donate_link'])) {106 if ( ! empty( $headers['donate_link'] ) ) { 105 107 $data->donate_link = $headers['donate_link']; 106 108 } 107 if ( !empty($headers['version'])) {109 if ( ! empty( $headers['version'] ) ) { 108 110 $data->version = $headers['version']; 109 } 110 else { 111 } else { 111 112 $data->version = $data->stable_tag; 112 113 } 113 114 114 115 // Parse the short description 115 while ( ($line = array_shift($contents)) !== null) {116 $trimmed = trim( $line);117 if ( empty($trimmed)) {116 while ( ( $line = array_shift( $contents ) ) !== null ) { 117 $trimmed = trim( $line ); 118 if ( empty( $trimmed ) ) { 118 119 $data->short_description .= "\n"; 119 120 continue; 120 121 } 121 if ( $trimmed[0] === '=' && isset($trimmed[1]) && $trimmed[1] === '=') {122 array_unshift( $contents, $line);122 if ( $trimmed[0] === '=' && isset( $trimmed[1] ) && $trimmed[1] === '=' ) { 123 array_unshift( $contents, $line ); 123 124 break; 124 125 } … … 126 127 $data->short_description .= $line . "\n"; 127 128 } 128 $data->short_description = trim($data->short_description); 129 130 $data->is_truncated = call_user_func_array(array($this_class, 'trim_short_desc'), array(&$data->short_description)); 129 $data->short_description = trim( $data->short_description ); 130 131 $data->is_truncated = call_user_func_array( array( 132 $this_class, 133 'trim_short_desc' 134 ), array( &$data->short_description ) ); 131 135 132 136 // Parse the rest of the body 133 137 $current = ''; 134 $special = array('description', 'installation', 'faq', 'frequently_asked_questions', 'screenshots', 'changelog', 'upgrade_notice'); 135 136 while (($line = array_shift($contents)) !== null) { 137 $trimmed = trim($line); 138 if (empty($trimmed)) { 138 $special = array( 139 'description', 140 'installation', 141 'faq', 142 'frequently_asked_questions', 143 'screenshots', 144 'changelog', 145 'upgrade_notice' 146 ); 147 148 while ( ( $line = array_shift( $contents ) ) !== null ) { 149 $trimmed = trim( $line ); 150 if ( empty( $trimmed ) ) { 139 151 $current .= "\n"; 140 152 continue; 141 153 } 142 154 143 if ( $trimmed[0] === '=' && isset($trimmed[1]) && $trimmed[1] === '=') {144 if ( !empty($title)) {145 $data->sections[ $title] = trim($current);146 } 147 148 $current = '';149 $real_title = trim( $line, "#= \t");150 $title = strtolower(str_replace(' ', '_', $real_title));151 if ( $title === 'faq') {155 if ( $trimmed[0] === '=' && isset( $trimmed[1] ) && $trimmed[1] === '=' ) { 156 if ( ! empty( $title ) ) { 157 $data->sections[ $title ] = trim( $current ); 158 } 159 160 $current = ''; 161 $real_title = trim( $line, "#= \t" ); 162 $title = strtolower( str_replace( ' ', '_', $real_title ) ); 163 if ( $title === 'faq' ) { 152 164 $title = 'frequently_asked_questions'; 153 } 154 elseif ($title === 'change_log') { 165 } elseif ( $title === 'change_log' ) { 155 166 $title = 'changelog'; 156 167 } 157 if ( !in_array($title, $special)) {168 if ( ! in_array( $title, $special ) ) { 158 169 $current .= '<h3>' . $real_title . "</h3>"; 159 170 } … … 164 175 } 165 176 166 if ( !empty($title)) {167 $data->sections[ $title] = trim($current);168 } 169 $title = null;177 if ( ! empty( $title ) ) { 178 $data->sections[ $title ] = trim( $current ); 179 } 180 $title = null; 170 181 $current = null; 171 182 172 if (empty($data->sections['description'])) { 173 $data->sections['description'] = call_user_func(array($this_class, 'parse_markdown'), $data->short_description); 183 if ( empty( $data->sections['description'] ) ) { 184 $data->sections['description'] = call_user_func( array( 185 $this_class, 186 'parse_markdown' 187 ), $data->short_description ); 174 188 } 175 189 176 190 // Parse changelog 177 if ( !empty($data->sections['changelog'])) {178 $lines = explode( "\n", $data->sections['changelog']);179 while ( ($line = array_shift($lines)) !== null) {180 $trimmed = trim( $line);181 if ( empty($trimmed)) {191 if ( ! empty( $data->sections['changelog'] ) ) { 192 $lines = explode( "\n", $data->sections['changelog'] ); 193 while ( ( $line = array_shift( $lines ) ) !== null ) { 194 $trimmed = trim( $line ); 195 if ( empty( $trimmed ) ) { 182 196 continue; 183 197 } 184 198 185 if ( $trimmed[0] === '=') {186 if ( !empty($current)) {187 $data->changelog[ $title] = trim($current);199 if ( $trimmed[0] === '=' ) { 200 if ( ! empty( $current ) ) { 201 $data->changelog[ $title ] = trim( $current ); 188 202 } 189 203 190 204 $current = ''; 191 $title = trim($line, "#= \t");205 $title = trim( $line, "#= \t" ); 192 206 continue; 193 207 } … … 196 210 } 197 211 198 $data->changelog[ $title] = trim($current);199 } 200 $title = null;212 $data->changelog[ $title ] = trim( $current ); 213 } 214 $title = null; 201 215 $current = null; 202 216 203 if ( isset($data->sections['upgrade_notice'])) {204 $lines = explode( "\n", $data->sections['upgrade_notice']);205 while ( ($line = array_shift($lines)) !== null) {206 $trimmed = trim( $line);207 if ( empty($trimmed)) {217 if ( isset( $data->sections['upgrade_notice'] ) ) { 218 $lines = explode( "\n", $data->sections['upgrade_notice'] ); 219 while ( ( $line = array_shift( $lines ) ) !== null ) { 220 $trimmed = trim( $line ); 221 if ( empty( $trimmed ) ) { 208 222 continue; 209 223 } 210 224 211 if ( $trimmed[0] === '=') {212 if ( !empty($current)) {213 $data->upgrade_notice[ $title] = trim($current);225 if ( $trimmed[0] === '=' ) { 226 if ( ! empty( $current ) ) { 227 $data->upgrade_notice[ $title ] = trim( $current ); 214 228 } 215 229 216 230 $current = ''; 217 $title = trim($line, "#= \t");231 $title = trim( $line, "#= \t" ); 218 232 continue; 219 233 } … … 222 236 } 223 237 224 if ( !empty($title) && !empty($current)) {225 $data->upgrade_notice[ $title] = trim($current);226 } 227 unset( $data->sections['upgrade_notice']);238 if ( ! empty( $title ) && ! empty( $current ) ) { 239 $data->upgrade_notice[ $title ] = trim( $current ); 240 } 241 unset( $data->sections['upgrade_notice'] ); 228 242 } 229 243 230 244 // Markdownify! 231 232 $data->sections = array_map(array($this_class, 'parse_markdown'), $data->sections); 233 $data->changelog = array_map(array($this_class, 'parse_markdown'), $data->changelog); 234 $data->upgrade_notice = array_map(array($this_class, 'parse_markdown'), $data->upgrade_notice); 235 236 if (isset($data->sections['screenshots'])) { 237 preg_match_all('#<li>(.*?)</li>#is', $data->sections['screenshots'], $screenshots, PREG_SET_ORDER); 238 if ($screenshots) { 239 foreach ((array) $screenshots as $ss) { 240 $data->screenshots[] = trim($ss[1]); 241 } 242 } 243 } 244 245 // Rearrange stuff 246 245 $data->sections = array_map( array( $this_class, 'parse_markdown' ), $data->sections ); 246 $data->changelog = array_map( array( $this_class, 'parse_markdown' ), $data->changelog ); 247 $data->upgrade_notice = array_map( array( $this_class, 'parse_markdown' ), $data->upgrade_notice ); 248 249 if ( isset( $data->sections['screenshots'] ) ) { 250 preg_match_all( '#<li>(.*?)</li>#is', $data->sections['screenshots'], $screenshots, PREG_SET_ORDER ); 251 if ( $screenshots ) { 252 foreach ( (array) $screenshots as $ss ) { 253 $data->screenshots[] = trim( $ss[1] ); 254 } 255 } 256 } 257 258 // Rearrange stuff. 247 259 $data->remaining_content = $data->sections; 248 $data->sections = array();249 250 foreach ( $special as $spec) {251 if ( isset($data->remaining_content[$spec])) {252 $data->sections[ $spec] = $data->remaining_content[$spec];253 unset( $data->remaining_content[$spec]);260 $data->sections = array(); 261 262 foreach ( $special as $spec ) { 263 if ( isset( $data->remaining_content[ $spec ] ) ) { 264 $data->sections[ $spec ] = $data->remaining_content[ $spec ]; 265 unset( $data->remaining_content[ $spec ] ); 254 266 } 255 267 } … … 258 270 } 259 271 260 protected static function get_first_nonwhitespace( &$contents) {261 while ( ($line = array_shift($contents)) !== null) {262 $trimmed = trim( $line);263 if ( !empty($line)) {272 protected static function get_first_nonwhitespace( &$contents ) { 273 while ( ( $line = array_shift( $contents ) ) !== null ) { 274 $trimmed = trim( $line ); 275 if ( ! empty( $line ) ) { 264 276 break; 265 277 } … … 269 281 } 270 282 271 protected static function strip_newlines($line) { 272 return rtrim($line, "\r\n"); 273 } 274 275 protected static function trim_short_desc(&$desc) { 276 if (function_exists('mb_strlen') && function_exists('mb_substr')) { 277 if (mb_strlen($desc) > 150) { 278 $desc = mb_substr($desc, 0, 150); 279 $desc = trim($desc); 283 protected static function strip_newlines( $line ) { 284 return rtrim( $line, "\r\n" ); 285 } 286 287 protected static function trim_short_desc( &$desc ) { 288 if ( function_exists( 'mb_strlen' ) && function_exists( 'mb_substr' ) ) { 289 if ( mb_strlen( $desc ) > 150 ) { 290 $desc = mb_substr( $desc, 0, 150 ); 291 $desc = trim( $desc ); 292 280 293 return true; 281 294 } 282 } 283 else{284 if (strlen($desc) > 150) {285 $desc = substr($desc, 0, 150);286 $desc = trim($desc); 295 } else { 296 if ( strlen( $desc ) > 150 ) { 297 $desc = substr( $desc, 0, 150 ); 298 $desc = trim( $desc ); 299 287 300 return true; 288 301 } … … 292 305 } 293 306 294 protected static function parse_markdown($text) { 295 $text = self::code_trick($text); 296 $text = preg_replace('/^[\s]*=[\s]+(.+?)[\s]+=/m', "\n" . '<h4>$1</h4>' . "\n", $text); 297 $text = Markdown(trim($text)); 298 return trim($text); 299 } 300 301 protected static function code_trick($text) { 307 protected static function parse_markdown( $text ) { 308 $text = self::code_trick( $text ); 309 $text = preg_replace( '/^[\s]*=[\s]+(.+?)[\s]+=/m', "\n" . '<h4>$1</h4>' . "\n", $text ); 310 $text = Markdown( trim( $text ) ); 311 312 return trim( $text ); 313 } 314 315 protected static function code_trick( $text ) { 302 316 // If doing markdown, first take any user formatted code blocks and turn them into backticks so that 303 317 // markdown will preserve things like underscores in code blocks 304 $text = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array(__CLASS__, 'decodeit'), $text);305 306 $text = str_replace(array("\r\n", "\r"), "\n", $text); 318 $text = preg_replace_callback( "!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__, 'decodeit' ), $text ); 319 $text = str_replace( array( "\r\n", "\r" ), "\n", $text ); 320 307 321 // Markdown can do inline code, we convert bbPress style block level code to Markdown style 308 $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array(__CLASS__, 'indent'), $text); 322 $text = preg_replace_callback( "!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent' ), $text ); 323 309 324 return $text; 310 325 } 311 326 312 protected static function indent( $matches) {327 protected static function indent( $matches ) { 313 328 $text = $matches[3]; 314 $text = preg_replace('|^|m', $matches[2] . ' ', $text); 329 $text = preg_replace( '|^|m', $matches[2] . ' ', $text ); 330 315 331 return $matches[1] . $text; 316 332 } 317 333 318 protected static function decodeit($matches) { 319 $text = $matches[2]; 320 $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES)); 321 $text = strtr($text, $trans_table); 322 $text = str_replace('<br />', '', $text); 323 $text = str_replace('&', '&', $text); 324 $text = str_replace(''', "'", $text); 325 if ( '<pre><code>' == $matches[1] ) 334 protected static function decodeit( $matches ) { 335 $text = $matches[2]; 336 $trans_table = array_flip( get_html_translation_table( HTML_ENTITIES ) ); 337 $text = strtr( $text, $trans_table ); 338 $text = str_replace( '<br />', '', $text ); 339 $text = str_replace( '&', '&', $text ); 340 $text = str_replace( ''', "'", $text ); 341 342 if ( '<pre><code>' == $matches[1] ) { 326 343 $text = "\n$text\n"; 344 } 345 327 346 return "`$text`"; 328 347 } -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme-parser/compat.php
r2499 r2501 1 1 <?php 2 /** 3 * WordPress.org Plugin Readme Parser. 4 * 5 * @package WPorg_Plugin_Directory 6 */ 2 7 3 if ( ! defined('WORDPRESS_README_MARKDOWN') ) {4 define( 'WORDPRESS_README_MARKDOWN', dirname(__FILE__) . '/markdown.php');8 if ( ! defined( 'WORDPRESS_README_MARKDOWN' ) ) { 9 define( 'WORDPRESS_README_MARKDOWN', dirname( __FILE__ ) . '/markdown.php' ); 5 10 } 6 11 7 require_once( dirname(__FILE__) . '/ReadmeParser.php');12 require_once( dirname( __FILE__ ) . '/ReadmeParser.php' ); 8 13 9 class _WordPress_org_Readme extends Baikonur_ReadmeParser { 10 public static function parse_readme($file) { 11 $contents = file($file); 12 return self::parse_readme_contents($contents); 14 /** 15 * Class WPorg_Readme 16 */ 17 class WPorg_Readme extends Baikonur_ReadmeParser { 18 19 /** 20 * @access public 21 * 22 * @param string $file File name. 23 * @return array 24 */ 25 public static function parse_readme( $file ) { 26 $contents = file( $file ); 27 28 return self::parse_readme_contents( $contents ); 13 29 } 14 30 15 public static function parse_readme_contents($contents) { 16 if (empty($contents)) { 31 /** 32 * @access public 33 * 34 * @param array $contents 35 * @return array 36 */ 37 public static function parse_readme_contents( $contents ) { 38 if ( empty( $contents ) ) { 17 39 return array(); 18 40 } 19 41 20 $result = parent::parse_readme_contents($contents); 21 foreach ($result->sections as &$section) { 22 $section = self::filter_text($section); 23 } 24 if (!empty($result->upgrade_notice)) { 25 foreach ($result->upgrade_notice as &$notice) { 26 $notice = self::sanitize_text($notice); 27 } 28 } 29 if (!empty($result->screenshots)) { 30 foreach ($result->screenshots as &$shot) { 31 $shot = self::filter_text($shot); 32 } 42 $result = parent::parse_readme_contents( $contents ); 43 $result->sections = array_map( array( 'WPorg_Readme', 'filter_text' ), $result->sections ); 44 45 if ( ! empty( $result->upgrade_notice ) ) { 46 $result->upgrade_notice = array_map( array( 'WPorg_Readme', 'sanitize_text' ), $result->upgrade_notice ); 33 47 } 34 48 35 if (!empty($result->remaining_content)) { 36 $result->remaining_content = implode("\n", $result->remaining_content); 37 $result->remaining_content = self::filter_text(str_replace("</h3>\n\n", "</h3>\n", $result->remaining_content)); 49 if ( ! empty( $result->screenshots ) ) { 50 $result->screenshots = array_map( array( 'WPorg_Readme', 'filter_text' ), $result->screenshots ); 38 51 } 39 else { 52 53 if ( ! empty( $result->remaining_content ) ) { 54 $result->remaining_content = implode( "\n", $result->remaining_content ); 55 $result->remaining_content = self::filter_text( str_replace( "</h3>\n\n", "</h3>\n", $result->remaining_content ) ); 56 } else { 40 57 $result->remaining_content = ''; 41 58 } 42 59 43 $result->name = self::sanitize_text( $result->name);60 $result->name = self::sanitize_text( $result->name ); 44 61 //$result->short_description = self::sanitize_text($result->short_description); 45 $result->donate_link = esc_url( $result->donate_link);62 $result->donate_link = esc_url( $result->donate_link ); 46 63 47 64 $result->requires_at_least = $result->requires; 48 $result->tested_up_to = $result->tested; 49 unset($result->requires, $result->tested); 50 $result = ((array) $result); 51 return $result; 65 $result->tested_up_to = $result->tested; 66 67 unset( $result->requires, $result->tested ); 68 69 return (array) $result; 52 70 } 53 71 54 protected static function trim_short_desc(&$desc) { 55 $desc = self::sanitize_text($desc); 56 return parent::trim_short_desc($desc); 72 /** 73 * @access protected 74 * 75 * @param string $desc 76 * @return string 77 */ 78 protected static function trim_short_desc( &$desc ) { 79 $desc = self::sanitize_text( $desc ); 80 parent::trim_short_desc( $desc ); 81 82 return $desc; 57 83 } 58 84 85 /** 86 * @access protected 87 * 88 * @param string $text 89 * @return string 90 */ 59 91 protected static function sanitize_text( $text ) { // not fancy 60 $text = strip_tags($text); 61 $text = esc_html($text); 62 $text = trim($text); 92 $text = strip_tags( $text ); 93 $text = esc_html( $text ); 94 $text = trim( $text ); 95 63 96 return $text; 64 97 } 65 98 99 /** 100 * @access protected 101 * 102 * @param string $text 103 * @return string 104 */ 66 105 protected static function filter_text( $text ) { 67 $text = trim( $text);106 $text = trim( $text ); 68 107 //$text = self::code_trick($text); // A better parser than Markdown's for: backticks -> CODE 69 108 70 109 $allowed = array( 71 'a' => array(72 'href' => array(),110 'a' => array( 111 'href' => array(), 73 112 'title' => array(), 74 'rel' => array()), 75 'blockquote' => array('cite' => array()), 76 'br' => array(), 77 'p' => array(), 78 'code' => array(), 79 'pre' => array(), 80 'em' => array(), 81 'strong' => array(), 82 'ul' => array(), 83 'ol' => array(), 84 'li' => array(), 85 'h3' => array(), 86 'h4' => array() 113 'rel' => array() 114 ), 115 'blockquote' => array( 'cite' => array() ), 116 'br' => array(), 117 'p' => array(), 118 'code' => array(), 119 'pre' => array(), 120 'em' => array(), 121 'strong' => array(), 122 'ul' => array(), 123 'ol' => array(), 124 'li' => array(), 125 'h3' => array(), 126 'h4' => array(), 87 127 ); 88 128 89 $text = balanceTags( $text);129 $text = balanceTags( $text ); 90 130 91 131 $text = wp_kses( $text, $allowed ); 92 $text = trim($text); 132 $text = trim( $text ); 133 93 134 return $text; 94 135 } -
sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/readme-parser/markdown.php
r2499 r2501 4 4 # 5 5 # PHP Markdown & Extra 6 # Copyright (c) 2004-2009 Michel Fortin 6 # Copyright (c) 2004-2009 Michel Fortin 7 7 # <http://michelf.com/projects/php-markdown/> 8 8 # 9 9 # Original Markdown 10 # Copyright (c) 2004-2006 John Gruber 10 # Copyright (c) 2004-2006 John Gruber 11 11 # <http://daringfireball.net/projects/markdown/> 12 12 # 13 13 14 15 define( 'MARKDOWN_VERSION', "1.0.1n" ); # Sat 10 Oct 2009 16 define( 'MARKDOWNEXTRA_VERSION', "1.2.4" ); # Sat 10 Oct 2009 17 14 define( 'MARKDOWN_VERSION', "1.0.1n" ); # Sat 10 Oct 2009 15 define( 'MARKDOWNEXTRA_VERSION', "1.2.4" ); # Sat 10 Oct 2009 18 16 19 17 # … … 22 20 23 21 # Change to ">" for HTML output 24 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />");22 @define( 'MARKDOWN_EMPTY_ELEMENT_SUFFIX', " />" ); 25 23 26 24 # Define the width of a tab for code blocks. 27 @define( 'MARKDOWN_TAB_WIDTH', 25 @define( 'MARKDOWN_TAB_WIDTH', 4 ); 28 26 29 27 # Optional title attribute for footnote links and backlinks. 30 @define( 'MARKDOWN_FN_LINK_TITLE', 31 @define( 'MARKDOWN_FN_BACKLINK_TITLE', 28 @define( 'MARKDOWN_FN_LINK_TITLE', "" ); 29 @define( 'MARKDOWN_FN_BACKLINK_TITLE', "" ); 32 30 33 31 # Optional class attribute for footnote links and backlinks. 34 @define( 'MARKDOWN_FN_LINK_CLASS', "" ); 35 @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); 36 32 @define( 'MARKDOWN_FN_LINK_CLASS', "" ); 33 @define( 'MARKDOWN_FN_BACKLINK_CLASS', "" ); 37 34 38 35 # … … 41 38 42 39 # Change to false to remove Markdown from posts and/or comments. 43 @define( 'MARKDOWN_WP_POSTS', true ); 44 @define( 'MARKDOWN_WP_COMMENTS', true ); 45 46 40 @define( 'MARKDOWN_WP_POSTS', true ); 41 @define( 'MARKDOWN_WP_COMMENTS', true ); 47 42 48 43 ### Standard Function Interface ### 49 44 50 @define( 'MARKDOWN_PARSER_CLASS', 51 52 function Markdown( $text) {45 @define( 'MARKDOWN_PARSER_CLASS', 'MarkdownExtra_Parser' ); 46 47 function Markdown( $text ) { 53 48 # 54 49 # Initialize the parser and return the result of its transform method. … … 56 51 # Setup static parser variable. 57 52 static $parser; 58 if ( !isset($parser)) {53 if ( ! isset( $parser ) ) { 59 54 $parser_class = MARKDOWN_PARSER_CLASS; 60 $parser = new $parser_class;55 $parser = new $parser_class; 61 56 } 62 57 63 58 # Transform text using parser. 64 return $parser->transform( $text);59 return $parser->transform( $text ); 65 60 } 66 67 61 68 62 ### WordPress Plugin Interface ### … … 77 71 */ 78 72 79 if ( isset($wp_version)) {73 if ( isset( $wp_version ) ) { 80 74 # More details about how it works here: 81 75 # <http://michelf.com/weblog/2005/wordpress-text-flow-vs-markdown/> 82 76 83 77 # Post content and excerpts 84 78 # - Remove WordPress paragraph generator. 85 79 # - Run Markdown on excerpt, then remove all tags. 86 80 # - Add paragraph tag around the excerpt, but remove it for the excerpt rss. 87 if ( MARKDOWN_WP_POSTS) {88 remove_filter( 'the_content', 'wpautop');89 remove_filter('the_content_rss', 'wpautop');90 remove_filter( 'the_excerpt', 'wpautop');91 add_filter( 'the_content', 'mdwp_MarkdownPost', 6);92 add_filter('the_content_rss', 'mdwp_MarkdownPost', 6);93 add_filter( 'get_the_excerpt', 'mdwp_MarkdownPost', 6);94 add_filter( 'get_the_excerpt', 'trim', 7);95 add_filter( 'the_excerpt', 'mdwp_add_p');96 add_filter( 'the_excerpt_rss', 'mdwp_strip_p');97 98 remove_filter( 'content_save_pre', 'balanceTags', 50);99 remove_filter( 'excerpt_save_pre', 'balanceTags', 50);100 add_filter( 'the_content', 'balanceTags', 50);101 add_filter( 'get_the_excerpt', 'balanceTags', 9);102 } 103 81 if ( MARKDOWN_WP_POSTS ) { 82 remove_filter( 'the_content', 'wpautop' ); 83 remove_filter( 'the_content_rss', 'wpautop' ); 84 remove_filter( 'the_excerpt', 'wpautop' ); 85 add_filter( 'the_content', 'mdwp_MarkdownPost', 6 ); 86 add_filter( 'the_content_rss', 'mdwp_MarkdownPost', 6 ); 87 add_filter( 'get_the_excerpt', 'mdwp_MarkdownPost', 6 ); 88 add_filter( 'get_the_excerpt', 'trim', 7 ); 89 add_filter( 'the_excerpt', 'mdwp_add_p' ); 90 add_filter( 'the_excerpt_rss', 'mdwp_strip_p' ); 91 92 remove_filter( 'content_save_pre', 'balanceTags', 50 ); 93 remove_filter( 'excerpt_save_pre', 'balanceTags', 50 ); 94 add_filter( 'the_content', 'balanceTags', 50 ); 95 add_filter( 'get_the_excerpt', 'balanceTags', 9 ); 96 } 97 104 98 # Add a footnote id prefix to posts when inside a loop. 105 function mdwp_MarkdownPost( $text) {99 function mdwp_MarkdownPost( $text ) { 106 100 static $parser; 107 if ( !$parser) {101 if ( ! $parser ) { 108 102 $parser_class = MARKDOWN_PARSER_CLASS; 109 $parser = new $parser_class;110 } 111 if ( is_single() || is_page() || is_feed()) {103 $parser = new $parser_class; 104 } 105 if ( is_single() || is_page() || is_feed() ) { 112 106 $parser->fn_id_prefix = ""; 113 107 } else { 114 108 $parser->fn_id_prefix = get_the_ID() . "."; 115 109 } 116 return $parser->transform($text); 117 } 118 110 111 return $parser->transform( $text ); 112 } 113 119 114 # Comments 120 115 # - Remove WordPress paragraph generator. … … 122 117 # - Scramble important tags before passing them to the kses filter. 123 118 # - Run Markdown on excerpt then remove paragraph tags. 124 if ( MARKDOWN_WP_COMMENTS) {125 remove_filter( 'comment_text', 'wpautop', 30);126 remove_filter( 'comment_text', 'make_clickable');127 add_filter( 'pre_comment_content', 'Markdown', 6);128 add_filter( 'pre_comment_content', 'mdwp_hide_tags', 8);129 add_filter( 'pre_comment_content', 'mdwp_show_tags', 12);130 add_filter( 'get_comment_text', 'Markdown', 6);131 add_filter( 'get_comment_excerpt', 'Markdown', 6);132 add_filter( 'get_comment_excerpt', 'mdwp_strip_p', 7);133 119 if ( MARKDOWN_WP_COMMENTS ) { 120 remove_filter( 'comment_text', 'wpautop', 30 ); 121 remove_filter( 'comment_text', 'make_clickable' ); 122 add_filter( 'pre_comment_content', 'Markdown', 6 ); 123 add_filter( 'pre_comment_content', 'mdwp_hide_tags', 8 ); 124 add_filter( 'pre_comment_content', 'mdwp_show_tags', 12 ); 125 add_filter( 'get_comment_text', 'Markdown', 6 ); 126 add_filter( 'get_comment_excerpt', 'Markdown', 6 ); 127 add_filter( 'get_comment_excerpt', 'mdwp_strip_p', 7 ); 128 134 129 global $mdwp_hidden_tags, $mdwp_placeholders; 135 $mdwp_hidden_tags = explode(' ', 136 '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>'); 137 $mdwp_placeholders = explode(' ', str_rot13( 138 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR '. 139 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli')); 140 } 141 142 function mdwp_add_p($text) { 143 if (!preg_match('{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text)) { 144 $text = '<p>'.$text.'</p>'; 145 $text = preg_replace('{\n{2,}}', "</p>\n\n<p>", $text); 146 } 130 $mdwp_hidden_tags = explode( ' ', 131 '<p> </p> <pre> </pre> <ol> </ol> <ul> </ul> <li> </li>' ); 132 $mdwp_placeholders = explode( ' ', str_rot13( 133 'pEj07ZbbBZ U1kqgh4w4p pre2zmeN6K QTi31t9pre ol0MP1jzJR ' . 134 'ML5IjmbRol ulANi1NsGY J7zRLJqPul liA8ctl16T K9nhooUHli' ) ); 135 } 136 137 function mdwp_add_p( $text ) { 138 if ( ! preg_match( '{^$|^<(p|ul|ol|dl|pre|blockquote)>}i', $text ) ) { 139 $text = '<p>' . $text . '</p>'; 140 $text = preg_replace( '{\n{2,}}', "</p>\n\n<p>", $text ); 141 } 142 147 143 return $text; 148 144 } 149 150 function mdwp_strip_p($t) { return preg_replace('{</?p>}i', '', $t); } 151 152 function mdwp_hide_tags($text) { 145 146 function mdwp_strip_p( $t ) { 147 return preg_replace( '{</?p>}i', '', $t ); 148 } 149 150 function mdwp_hide_tags( $text ) { 153 151 global $mdwp_hidden_tags, $mdwp_placeholders; 154 return str_replace($mdwp_hidden_tags, $mdwp_placeholders, $text); 155 } 156 function mdwp_show_tags($text) { 152 153 return str_replace( $mdwp_hidden_tags, $mdwp_placeholders, $text ); 154 } 155 156 function mdwp_show_tags( $text ) { 157 157 global $mdwp_hidden_tags, $mdwp_placeholders; 158 return str_replace($mdwp_placeholders, $mdwp_hidden_tags, $text); 158 159 return str_replace( $mdwp_placeholders, $mdwp_hidden_tags, $text ); 159 160 } 160 161 } 161 162 162 163 163 ### bBlog Plugin Info ### … … 165 165 function identify_modifier_markdown() { 166 166 return array( 167 'name' => 'markdown',168 'type' => 'modifier',169 'nicename' => 'PHP Markdown Extra',167 'name' => 'markdown', 168 'type' => 'modifier', 169 'nicename' => 'PHP Markdown Extra', 170 170 'description' => 'A text-to-HTML conversion tool for web writers', 171 'authors' => 'Michel Fortin and John Gruber',172 'licence' => 'GPL',173 'version' => MARKDOWNEXTRA_VERSION,174 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.com/projects/php-markdown/">More...</a>',175 171 'authors' => 'Michel Fortin and John Gruber', 172 'licence' => 'GPL', 173 'version' => MARKDOWNEXTRA_VERSION, 174 'help' => '<a href="http://daringfireball.net/projects/markdown/syntax">Markdown syntax</a> allows you to write using an easy-to-read, easy-to-write plain text format. Based on the original Perl version by <a href="http://daringfireball.net/">John Gruber</a>. <a href="http://michelf.com/projects/php-markdown/">More...</a>', 175 ); 176 176 } 177 177 178 179 178 ### Smarty Modifier Interface ### 180 179 181 function smarty_modifier_markdown( $text) {182 return Markdown( $text);180 function smarty_modifier_markdown( $text ) { 181 return Markdown( $text ); 183 182 } 184 183 185 186 184 ### Textile Compatibility Mode ### 187 185 188 186 # Rename this file to "classTextile.php" and it can replace Textile everywhere. 189 187 190 if ( strcasecmp(substr(__FILE__, -16), "classTextile.php") == 0) {188 if ( strcasecmp( substr( __FILE__, - 16 ), "classTextile.php" ) == 0 ) { 191 189 # Try to include PHP SmartyPants. Should be in the same directory. 192 190 @include_once 'smartypants.php'; 191 193 192 # Fake Textile class. It calls Markdown instead. 194 193 class Textile { 195 function TextileThis($text, $lite='', $encode='') { 196 if ($lite == '' && $encode == '') $text = Markdown($text); 197 if (function_exists('SmartyPants')) $text = SmartyPants($text); 194 function TextileThis( $text, $lite = '', $encode = '' ) { 195 if ( $lite == '' && $encode == '' ) { 196 $text = Markdown( $text ); 197 } 198 if ( function_exists( 'SmartyPants' ) ) { 199 $text = SmartyPants( $text ); 200 } 201 198 202 return $text; 199 203 } 204 200 205 # Fake restricted version: restrictions are not supported for now. 201 function TextileRestricted($text, $lite='', $noimage='') { 202 return $this->TextileThis($text, $lite); 203 } 206 function TextileRestricted( $text, $lite = '', $noimage = '' ) { 207 return $this->TextileThis( $text, $lite ); 208 } 209 204 210 # Workaround to ensure compatibility with TextPattern 4.0.3. 205 function blockLite($text) { return $text; } 211 function blockLite( $text ) { 212 return $text; 213 } 206 214 } 207 215 } 208 209 210 216 211 217 # … … 214 220 215 221 class Markdown_Parser { 216 217 222 # Regex to match balanced [brackets]. 218 223 # Needed to insert a maximum bracked depth while converting to PHP. 219 224 var $nested_brackets_depth = 6; 220 225 var $nested_brackets_re; 221 222 226 var $nested_url_parenthesis_depth = 4; 223 227 var $nested_url_parenthesis_re; 224 225 228 # Table of hash values for escaped characters: 226 229 var $escape_chars = '\`*_{}[]()>#+-.!'; 227 230 var $escape_chars_re; 228 229 231 # Change to ">" for HTML output. 230 232 var $empty_element_suffix = MARKDOWN_EMPTY_ELEMENT_SUFFIX; 231 233 var $tab_width = MARKDOWN_TAB_WIDTH; 232 233 234 # Change to `true` to disallow markup or entities. 234 235 var $no_markup = false; 235 236 var $no_entities = false; 236 237 237 # Predefined urls and titles for reference links and images. 238 238 var $predef_urls = array(); 239 239 var $predef_titles = array(); 240 240 241 242 241 function Markdown_Parser() { 243 #244 # Constructor function. Initialize appropriate member variables.245 #242 # 243 # Constructor function. Initialize appropriate member variables. 244 # 246 245 $this->_initDetab(); 247 246 $this->prepareItalicsAndBold(); 248 249 $this->nested_brackets_re = 250 str_repeat( '(?>[^\[\]]+|\[', $this->nested_brackets_depth).251 str_repeat( '\])*', $this->nested_brackets_depth);252 253 $this->nested_url_parenthesis_re = 254 str_repeat( '(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).255 str_repeat( '(?>\)))*', $this->nested_url_parenthesis_depth);256 257 $this->escape_chars_re = '[' .preg_quote($this->escape_chars).']';258 247 248 $this->nested_brackets_re = 249 str_repeat( '(?>[^\[\]]+|\[', $this->nested_brackets_depth ) . 250 str_repeat( '\])*', $this->nested_brackets_depth ); 251 252 $this->nested_url_parenthesis_re = 253 str_repeat( '(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth ) . 254 str_repeat( '(?>\)))*', $this->nested_url_parenthesis_depth ); 255 256 $this->escape_chars_re = '[' . preg_quote( $this->escape_chars ) . ']'; 257 259 258 # Sort document, block, and span gamut in ascendent priority order. 260 asort($this->document_gamut); 261 asort($this->block_gamut); 262 asort($this->span_gamut); 263 } 264 259 asort( $this->document_gamut ); 260 asort( $this->block_gamut ); 261 asort( $this->span_gamut ); 262 } 265 263 266 264 # Internal hashes used during transformation. … … 268 266 var $titles = array(); 269 267 var $html_hashes = array(); 270 271 268 # Status flag to avoid invalid nesting. 272 269 var $in_anchor = false; 273 274 270 275 271 function setup() { 276 #277 # Called before the transformation process starts to setup parser278 # states.279 #272 # 273 # Called before the transformation process starts to setup parser 274 # states. 275 # 280 276 # Clear global hashes. 281 $this->urls = $this->predef_urls;282 $this->titles = $this->predef_titles;277 $this->urls = $this->predef_urls; 278 $this->titles = $this->predef_titles; 283 279 $this->html_hashes = array(); 284 280 285 281 $in_anchor = false; 286 282 } 287 283 288 284 function teardown() { 289 #290 # Called after the transformation process to clear any variable291 # which may be taking up memory unnecessarly.292 #293 $this->urls = array();294 $this->titles = array();285 # 286 # Called after the transformation process to clear any variable 287 # which may be taking up memory unnecessarly. 288 # 289 $this->urls = array(); 290 $this->titles = array(); 295 291 $this->html_hashes = array(); 296 292 } 297 293 298 299 function transform($text) { 300 # 301 # Main function. Performs some preprocessing on the input text 302 # and pass it through the document gamut. 303 # 294 function transform( $text ) { 295 # 296 # Main function. Performs some preprocessing on the input text 297 # and pass it through the document gamut. 298 # 304 299 $this->setup(); 305 300 306 301 # Remove UTF-8 BOM and marker character in input, if present. 307 $text = preg_replace( '{^\xEF\xBB\xBF|\x1A}', '', $text);302 $text = preg_replace( '{^\xEF\xBB\xBF|\x1A}', '', $text ); 308 303 309 304 # Standardize line endings: 310 305 # DOS to Unix and Mac to Unix 311 $text = preg_replace( '{\r\n?}', "\n", $text);306 $text = preg_replace( '{\r\n?}', "\n", $text ); 312 307 313 308 # Make sure $text ends with a couple of newlines: … … 315 310 316 311 # Convert all tabs to spaces. 317 $text = $this->detab( $text);312 $text = $this->detab( $text ); 318 313 319 314 # Turn block-level HTML blocks into hash entries 320 $text = $this->hashHTMLBlocks( $text);315 $text = $this->hashHTMLBlocks( $text ); 321 316 322 317 # Strip any lines consisting only of spaces and tabs. … … 324 319 # match consecutive blank lines with /\n+/ instead of something 325 320 # contorted like /[ ]*\n+/ . 326 $text = preg_replace( '/^[ ]+$/m', '', $text);321 $text = preg_replace( '/^[ ]+$/m', '', $text ); 327 322 328 323 # Run document gamut methods. 329 foreach ( $this->document_gamut as $method => $priority) {330 $text = $this->$method( $text);331 } 332 324 foreach ( $this->document_gamut as $method => $priority ) { 325 $text = $this->$method( $text ); 326 } 327 333 328 $this->teardown(); 334 329 335 330 return $text . "\n"; 336 331 } 337 332 338 333 var $document_gamut = array( 339 334 # Strip link definitions, store in hashes. 340 335 "stripLinkDefinitions" => 20, 341 342 336 "runBasicBlockGamut" => 30, 343 ); 344 345 346 function stripLinkDefinitions($text) { 347 # 348 # Strips link definitions from text, stores the URLs and titles in 349 # hash references. 350 # 337 ); 338 339 function stripLinkDefinitions( $text ) { 340 # 341 # Strips link definitions from text, stores the URLs and titles in 342 # hash references. 343 # 351 344 $less_than_tab = $this->tab_width - 1; 352 345 353 346 # Link defs are in the form: ^[id]: url "optional title" 354 $text = preg_replace_callback( '{355 ^[ ]{0,' .$less_than_tab.'}\[(.+)\][ ]?: # id = $1347 $text = preg_replace_callback( '{ 348 ^[ ]{0,' . $less_than_tab . '}\[(.+)\][ ]?: # id = $1 356 349 [ ]* 357 350 \n? # maybe *one* newline … … 374 367 (?:\n+|\Z) 375 368 }xm', 376 array(&$this, '_stripLinkDefinitions_callback'), 377 $text); 369 array( &$this, '_stripLinkDefinitions_callback' ), 370 $text ); 371 378 372 return $text; 379 373 } 380 function _stripLinkDefinitions_callback($matches) { 381 $link_id = strtolower($matches[1]); 382 $url = $matches[2] == '' ? $matches[3] : $matches[2]; 383 $this->urls[$link_id] = $url; 384 $this->titles[$link_id] =& $matches[4]; 374 375 function _stripLinkDefinitions_callback( $matches ) { 376 $link_id = strtolower( $matches[1] ); 377 $url = $matches[2] == '' ? $matches[3] : $matches[2]; 378 $this->urls[ $link_id ] = $url; 379 $this->titles[ $link_id ] =& $matches[4]; 380 385 381 return ''; # String that will replace the block 386 382 } 387 383 388 389 function hashHTMLBlocks($text) { 390 if ($this->no_markup) return $text; 384 function hashHTMLBlocks( $text ) { 385 if ( $this->no_markup ) { 386 return $text; 387 } 391 388 392 389 $less_than_tab = $this->tab_width - 1; … … 400 397 # 401 398 # * List "a" is made of tags which can be both inline or block-level. 402 # These will be treated block-level when the start tag is alone on 403 # its line, otherwise they're not matched here and will be taken as 399 # These will be treated block-level when the start tag is alone on 400 # its line, otherwise they're not matched here and will be taken as 404 401 # inline later. 405 402 # * List "b" is made of tags which are always block-level; 406 403 # 407 404 $block_tags_a_re = 'ins|del'; 408 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' .409 405 $block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|' . 406 'script|noscript|form|fieldset|iframe|math'; 410 407 411 408 # Regular expression for the content of a block tag. 412 409 $nested_tags_level = 4; 413 $attr = '410 $attr = ' 414 411 (?> # optional tag attributes 415 412 \s # starts with whitespace … … 423 420 \'[^\']*\' # text inside single quotes (tolerate ">") 424 421 )* 425 )? 422 )? 426 423 '; 427 $content =428 str_repeat( '424 $content = 425 str_repeat( ' 429 426 (?> 430 427 [^<]+ # content without tag 431 428 | 432 429 <\2 # nested opening tag 433 ' .$attr.' # attributes430 ' . $attr . ' # attributes 434 431 (?> 435 432 /> 436 433 | 437 >', $nested_tags_level ).# end of opening tag438 '.*?'.# last level nested tag content439 str_repeat( '434 >', $nested_tags_level ) . # end of opening tag 435 '.*?' . # last level nested tag content 436 str_repeat( ' 440 437 </\2\s*> # closing nested tag 441 438 ) 442 | 439 | 443 440 <(?!/\2\s*> # other tags with a different name 444 441 ) 445 442 )*', 446 $nested_tags_level );447 $content2 = str_replace('\2', '\3', $content);443 $nested_tags_level ); 444 $content2 = str_replace( '\2', '\3', $content ); 448 445 449 446 # First, look for nested blocks, e.g.: … … 458 455 # We need to do this before the next, more liberal match, because the next 459 456 # match will start at the first `<div>` and stop at the first `</div>`. 460 $text = preg_replace_callback( '{(?>457 $text = preg_replace_callback( '{(?> 461 458 (?> 462 459 (?<=\n\n) # Starting after a blank line … … 466 463 ( # save in $1 467 464 468 # Match from `\n<tag>` to `</tag>\n`, handling nested tags 465 # Match from `\n<tag>` to `</tag>\n`, handling nested tags 469 466 # in between. 470 471 [ ]{0,' .$less_than_tab.'}472 <(' .$block_tags_b_re.')# start tag = $2473 ' .$attr.'> # attributes followed by > and \n474 ' .$content.' # content, support nesting467 468 [ ]{0,' . $less_than_tab . '} 469 <(' . $block_tags_b_re . ')# start tag = $2 470 ' . $attr . '> # attributes followed by > and \n 471 ' . $content . ' # content, support nesting 475 472 </\2> # the matching end tag 476 473 [ ]* # trailing spaces/tabs … … 479 476 | # Special version for tags of group a. 480 477 481 [ ]{0,' .$less_than_tab.'}482 <(' .$block_tags_a_re.')# start tag = $3483 ' .$attr.'>[ ]*\n # attributes followed by >484 ' .$content2.' # content, support nesting478 [ ]{0,' . $less_than_tab . '} 479 <(' . $block_tags_a_re . ')# start tag = $3 480 ' . $attr . '>[ ]*\n # attributes followed by > 481 ' . $content2 . ' # content, support nesting 485 482 </\3> # the matching end tag 486 483 [ ]* # trailing spaces/tabs 487 484 (?=\n+|\Z) # followed by a newline or end of document 488 489 | # Special case just for <hr />. It was easier to make a special 485 486 | # Special case just for <hr />. It was easier to make a special 490 487 # case than to make the other regex more complicated. 491 492 [ ]{0,' .$less_than_tab.'}488 489 [ ]{0,' . $less_than_tab . '} 493 490 <(hr) # start tag = $2 494 ' .$attr.' # attributes491 ' . $attr . ' # attributes 495 492 /?> # the matching end tag 496 493 [ ]* 497 494 (?=\n{2,}|\Z) # followed by a blank line or end of document 498 495 499 496 | # Special case for standalone HTML comments: 500 501 [ ]{0,' .$less_than_tab.'}497 498 [ ]{0,' . $less_than_tab . '} 502 499 (?s: 503 500 <!-- .*? --> … … 505 502 [ ]* 506 503 (?=\n{2,}|\Z) # followed by a blank line or end of document 507 504 508 505 | # PHP and ASP-style processor instructions (<? and <%) 509 510 [ ]{0,' .$less_than_tab.'}506 507 [ ]{0,' . $less_than_tab . '} 511 508 (?s: 512 509 <([?%]) # $2 … … 516 513 [ ]* 517 514 (?=\n{2,}|\Z) # followed by a blank line or end of document 518 515 519 516 ) 520 517 )}Sxmi', 521 array( &$this, '_hashHTMLBlocks_callback'),522 $text );518 array( &$this, '_hashHTMLBlocks_callback' ), 519 $text ); 523 520 524 521 return $text; 525 522 } 526 function _hashHTMLBlocks_callback($matches) { 523 524 function _hashHTMLBlocks_callback( $matches ) { 527 525 $text = $matches[1]; 528 $key = $this->hashBlock($text); 526 $key = $this->hashBlock( $text ); 527 529 528 return "\n\n$key\n\n"; 530 529 } 531 532 533 function hashPart($text, $boundary = 'X') { 534 # 535 # Called whenever a tag must be hashed when a function insert an atomic 536 # element in the text stream. Passing $text to through this function gives 537 # a unique text-token which will be reverted back when calling unhash. 538 # 539 # The $boundary argument specify what character should be used to surround 540 # the token. By convension, "B" is used for block elements that needs not 541 # to be wrapped into paragraph tags at the end, ":" is used for elements 542 # that are word separators and "X" is used in the general case. 543 # 530 531 function hashPart( $text, $boundary = 'X' ) { 532 # 533 # Called whenever a tag must be hashed when a function insert an atomic 534 # element in the text stream. Passing $text to through this function gives 535 # a unique text-token which will be reverted back when calling unhash. 536 # 537 # The $boundary argument specify what character should be used to surround 538 # the token. By convension, "B" is used for block elements that needs not 539 # to be wrapped into paragraph tags at the end, ":" is used for elements 540 # that are word separators and "X" is used in the general case. 541 # 544 542 # Swap back any tag hash found in $text so we do not have to `unhash` 545 543 # multiple times at the end. 546 $text = $this->unhash( $text);547 544 $text = $this->unhash( $text ); 545 548 546 # Then hash the block. 549 547 static $i = 0; 550 $key = "$boundary\x1A" . ++$i . $boundary; 551 $this->html_hashes[$key] = $text; 548 $key = "$boundary\x1A" . ++ $i . $boundary; 549 $this->html_hashes[ $key ] = $text; 550 552 551 return $key; # String that will replace the tag. 553 552 } 554 553 555 556 function hashBlock($text) { 557 # 558 # Shortcut function for hashPart with block-level boundaries. 559 # 560 return $this->hashPart($text, 'B'); 561 } 562 554 function hashBlock( $text ) { 555 # 556 # Shortcut function for hashPart with block-level boundaries. 557 # 558 return $this->hashPart( $text, 'B' ); 559 } 563 560 564 561 var $block_gamut = array( 565 #566 # These are all the transformations that form block-level567 # tags like paragraphs, headers, and list items.568 #562 # 563 # These are all the transformations that form block-level 564 # tags like paragraphs, headers, and list items. 565 # 569 566 "doHeaders" => 10, 570 567 "doHorizontalRules" => 20, 571 572 568 "doLists" => 40, 573 569 "doCodeBlocks" => 50, 574 570 "doBlockQuotes" => 60, 575 576 577 function runBlockGamut( $text) {578 #579 # Run block gamut tranformations.580 #581 # We need to escape raw HTML in Markdown source before doing anything 582 # else. This need to be done for each block, and not only at the 571 ); 572 573 function runBlockGamut( $text ) { 574 # 575 # Run block gamut tranformations. 576 # 577 # We need to escape raw HTML in Markdown source before doing anything 578 # else. This need to be done for each block, and not only at the 583 579 # begining in the Markdown function since hashed blocks can be part of 584 # list items and could have been indented. Indented blocks would have 580 # list items and could have been indented. Indented blocks would have 585 581 # been seen as a code block in a previous pass of hashHTMLBlocks. 586 $text = $this->hashHTMLBlocks( $text);587 588 return $this->runBasicBlockGamut( $text);589 } 590 591 function runBasicBlockGamut( $text) {592 #593 # Run block gamut tranformations, without hashing HTML blocks. This is594 # useful when HTML blocks are known to be already hashed, like in the first595 # whole-document pass.596 #597 foreach ( $this->block_gamut as $method => $priority) {598 $text = $this->$method( $text);599 } 600 582 $text = $this->hashHTMLBlocks( $text ); 583 584 return $this->runBasicBlockGamut( $text ); 585 } 586 587 function runBasicBlockGamut( $text ) { 588 # 589 # Run block gamut tranformations, without hashing HTML blocks. This is 590 # useful when HTML blocks are known to be already hashed, like in the first 591 # whole-document pass. 592 # 593 foreach ( $this->block_gamut as $method => $priority ) { 594 $text = $this->$method( $text ); 595 } 596 601 597 # Finally form paragraph and restore hashed blocks. 602 $text = $this->formParagraphs( $text);598 $text = $this->formParagraphs( $text ); 603 599 604 600 return $text; 605 601 } 606 607 608 function doHorizontalRules($text) { 602 603 function doHorizontalRules( $text ) { 609 604 # Do Horizontal Rules: 610 605 return preg_replace( … … 619 614 $ # End of line. 620 615 }mx', 621 "\n".$this->hashBlock("<hr$this->empty_element_suffix")."\n", 622 $text); 623 } 624 616 "\n" . $this->hashBlock( "<hr$this->empty_element_suffix" ) . "\n", 617 $text ); 618 } 625 619 626 620 var $span_gamut = array( 627 #628 # These are all the transformations that occur *within* block-level629 # tags like paragraphs, headers, and list items.630 #621 # 622 # These are all the transformations that occur *within* block-level 623 # tags like paragraphs, headers, and list items. 624 # 631 625 # Process character escapes, code spans, and inline HTML 632 626 # in one shot. 633 "parseSpan" => -30, 634 627 "parseSpan" => - 30, 635 628 # Process anchor and image tags. Images must come first, 636 629 # because ![foo][f] looks like an anchor. 637 "doImages" => 10, 638 "doAnchors" => 20, 639 630 "doImages" => 10, 631 "doAnchors" => 20, 640 632 # Make links out of things like `<http://example.com/>` 641 633 # Must come after doAnchors, because you can use < and > 642 634 # delimiters in inline links like [this](<url>). 643 "doAutoLinks" => 30, 644 "encodeAmpsAndAngles" => 40, 645 646 "doItalicsAndBold" => 50, 647 "doHardBreaks" => 60, 648 ); 649 650 function runSpanGamut($text) { 651 # 652 # Run span gamut tranformations. 653 # 654 foreach ($this->span_gamut as $method => $priority) { 655 $text = $this->$method($text); 635 "doAutoLinks" => 30, 636 "encodeAmpsAndAngles" => 40, 637 "doItalicsAndBold" => 50, 638 "doHardBreaks" => 60, 639 ); 640 641 function runSpanGamut( $text ) { 642 # 643 # Run span gamut tranformations. 644 # 645 foreach ( $this->span_gamut as $method => $priority ) { 646 $text = $this->$method( $text ); 656 647 } 657 648 658 649 return $text; 659 650 } 660 661 662 function doHardBreaks($text) { 651 652 function doHardBreaks( $text ) { 663 653 # Do hard breaks: 664 return preg_replace_callback('/ {2,}\n/', 665 array(&$this, '_doHardBreaks_callback'), $text); 666 } 667 function _doHardBreaks_callback($matches) { 668 return $this->hashPart("<br$this->empty_element_suffix\n"); 669 } 670 671 672 function doAnchors($text) { 673 # 674 # Turn Markdown link shortcuts into XHTML <a> tags. 675 # 676 if ($this->in_anchor) return $text; 654 return preg_replace_callback( '/ {2,}\n/', 655 array( &$this, '_doHardBreaks_callback' ), $text ); 656 } 657 658 function _doHardBreaks_callback( $matches ) { 659 return $this->hashPart( "<br$this->empty_element_suffix\n" ); 660 } 661 662 function doAnchors( $text ) { 663 # 664 # Turn Markdown link shortcuts into XHTML <a> tags. 665 # 666 if ( $this->in_anchor ) { 667 return $text; 668 } 677 669 $this->in_anchor = true; 678 670 679 671 # 680 672 # First, handle reference-style links: [link text] [id] 681 673 # 682 $text = preg_replace_callback( '{674 $text = preg_replace_callback( '{ 683 675 ( # wrap whole match in $1 684 676 \[ 685 (' .$this->nested_brackets_re.') # link text = $2677 (' . $this->nested_brackets_re . ') # link text = $2 686 678 \] 687 679 … … 694 686 ) 695 687 }xs', 696 array( &$this, '_doAnchors_reference_callback'), $text);688 array( &$this, '_doAnchors_reference_callback' ), $text ); 697 689 698 690 # 699 691 # Next, inline-style links: [link text](url "optional title") 700 692 # 701 $text = preg_replace_callback( '{693 $text = preg_replace_callback( '{ 702 694 ( # wrap whole match in $1 703 695 \[ 704 (' .$this->nested_brackets_re.') # link text = $2696 (' . $this->nested_brackets_re . ') # link text = $2 705 697 \] 706 698 \( # literal paren … … 709 701 <(.+?)> # href = $3 710 702 | 711 (' .$this->nested_url_parenthesis_re.') # href = $4703 (' . $this->nested_url_parenthesis_re . ') # href = $4 712 704 ) 713 705 [ \n]* … … 721 713 ) 722 714 }xs', 723 array( &$this, '_doAnchors_inline_callback'), $text);715 array( &$this, '_doAnchors_inline_callback' ), $text ); 724 716 725 717 # … … 728 720 # or [link text](/foo) 729 721 # 730 $text = preg_replace_callback( '{722 $text = preg_replace_callback( '{ 731 723 ( # wrap whole match in $1 732 724 \[ … … 735 727 ) 736 728 }xs', 737 array( &$this, '_doAnchors_reference_callback'), $text);729 array( &$this, '_doAnchors_reference_callback' ), $text ); 738 730 739 731 $this->in_anchor = false; 732 740 733 return $text; 741 734 } 742 function _doAnchors_reference_callback($matches) { 743 $whole_match = $matches[1]; 744 $link_text = $matches[2]; 735 736 function _doAnchors_reference_callback( $matches ) { 737 $whole_match = $matches[1]; 738 $link_text = $matches[2]; 745 739 $link_id =& $matches[3]; 746 740 747 if ( $link_id == "") {741 if ( $link_id == "" ) { 748 742 # for shortcut links like [this][] or [this]. 749 743 $link_id = $link_text; 750 744 } 751 745 752 746 # lower-case and turn embedded newlines into spaces 753 $link_id = strtolower( $link_id);754 $link_id = preg_replace( '{[ ]?\n}', ' ', $link_id);755 756 if ( isset($this->urls[$link_id])) {757 $url = $this->urls[ $link_id];758 $url = $this->encodeAttribute( $url);759 747 $link_id = strtolower( $link_id ); 748 $link_id = preg_replace( '{[ ]?\n}', ' ', $link_id ); 749 750 if ( isset( $this->urls[ $link_id ] ) ) { 751 $url = $this->urls[ $link_id ]; 752 $url = $this->encodeAttribute( $url ); 753 760 754 $result = "<a href=\"$url\""; 761 if ( isset( $this->titles[ $link_id] ) ) {762 $title = $this->titles[ $link_id];763 $title = $this->encodeAttribute( $title);764 $result .= 765 } 766 767 $link_text = $this->runSpanGamut( $link_text);755 if ( isset( $this->titles[ $link_id ] ) ) { 756 $title = $this->titles[ $link_id ]; 757 $title = $this->encodeAttribute( $title ); 758 $result .= " title=\"$title\""; 759 } 760 761 $link_text = $this->runSpanGamut( $link_text ); 768 762 $result .= ">$link_text</a>"; 769 $result = $this->hashPart($result); 770 } 771 else { 763 $result = $this->hashPart( $result ); 764 } else { 772 765 $result = $whole_match; 773 766 } 767 774 768 return $result; 775 769 } 776 function _doAnchors_inline_callback($matches) { 777 $whole_match = $matches[1]; 778 $link_text = $this->runSpanGamut($matches[2]); 779 $url = $matches[3] == '' ? $matches[4] : $matches[3]; 780 $title =& $matches[7]; 781 782 $url = $this->encodeAttribute($url); 770 771 function _doAnchors_inline_callback( $matches ) { 772 $whole_match = $matches[1]; 773 $link_text = $this->runSpanGamut( $matches[2] ); 774 $url = $matches[3] == '' ? $matches[4] : $matches[3]; 775 $title =& $matches[7]; 776 777 $url = $this->encodeAttribute( $url ); 783 778 784 779 $result = "<a href=\"$url\""; 785 if ( isset($title)) {786 $title = $this->encodeAttribute( $title);787 $result .= 788 } 789 790 $link_text = $this->runSpanGamut( $link_text);780 if ( isset( $title ) ) { 781 $title = $this->encodeAttribute( $title ); 782 $result .= " title=\"$title\""; 783 } 784 785 $link_text = $this->runSpanGamut( $link_text ); 791 786 $result .= ">$link_text</a>"; 792 787 793 return $this->hashPart($result); 794 } 795 796 797 function doImages($text) { 798 # 799 # Turn Markdown image shortcuts into <img> tags. 800 # 788 return $this->hashPart( $result ); 789 } 790 791 function doImages( $text ) { 792 # 793 # Turn Markdown image shortcuts into <img> tags. 794 # 801 795 # 802 796 # First, handle reference-style labeled images: ![alt text][id] 803 797 # 804 $text = preg_replace_callback( '{798 $text = preg_replace_callback( '{ 805 799 ( # wrap whole match in $1 806 800 !\[ 807 (' .$this->nested_brackets_re.') # alt text = $2801 (' . $this->nested_brackets_re . ') # alt text = $2 808 802 \] 809 803 … … 816 810 817 811 ) 818 }xs', 819 array( &$this, '_doImages_reference_callback'), $text);812 }xs', 813 array( &$this, '_doImages_reference_callback' ), $text ); 820 814 821 815 # … … 823 817 # Don't forget: encode * and _ 824 818 # 825 $text = preg_replace_callback( '{819 $text = preg_replace_callback( '{ 826 820 ( # wrap whole match in $1 827 821 !\[ 828 (' .$this->nested_brackets_re.') # alt text = $2822 (' . $this->nested_brackets_re . ') # alt text = $2 829 823 \] 830 824 \s? # One optional whitespace character … … 834 828 <(\S*)> # src url = $3 835 829 | 836 (' .$this->nested_url_parenthesis_re.') # src url = $4830 (' . $this->nested_url_parenthesis_re . ') # src url = $4 837 831 ) 838 832 [ \n]* … … 846 840 ) 847 841 }xs', 848 array( &$this, '_doImages_inline_callback'), $text);842 array( &$this, '_doImages_inline_callback' ), $text ); 849 843 850 844 return $text; 851 845 } 852 function _doImages_reference_callback($matches) { 846 847 function _doImages_reference_callback( $matches ) { 853 848 $whole_match = $matches[1]; 854 849 $alt_text = $matches[2]; 855 $link_id = strtolower( $matches[3]);856 857 if ( $link_id == "") {858 $link_id = strtolower( $alt_text); # for shortcut links like ![this][].859 } 860 861 $alt_text = $this->encodeAttribute( $alt_text);862 if ( isset($this->urls[$link_id])) {863 $url = $this->encodeAttribute($this->urls[$link_id]);850 $link_id = strtolower( $matches[3] ); 851 852 if ( $link_id == "" ) { 853 $link_id = strtolower( $alt_text ); # for shortcut links like ![this][]. 854 } 855 856 $alt_text = $this->encodeAttribute( $alt_text ); 857 if ( isset( $this->urls[ $link_id ] ) ) { 858 $url = $this->encodeAttribute( $this->urls[ $link_id ] ); 864 859 $result = "<img src=\"$url\" alt=\"$alt_text\""; 865 if ( isset($this->titles[$link_id])) {866 $title = $this->titles[ $link_id];867 $title = $this->encodeAttribute( $title);868 $result .= 860 if ( isset( $this->titles[ $link_id ] ) ) { 861 $title = $this->titles[ $link_id ]; 862 $title = $this->encodeAttribute( $title ); 863 $result .= " title=\"$title\""; 869 864 } 870 865 $result .= $this->empty_element_suffix; 871 $result = $this->hashPart($result); 872 } 873 else { 866 $result = $this->hashPart( $result ); 867 } else { 874 868 # If there's no such link ID, leave intact: 875 869 $result = $whole_match; … … 878 872 return $result; 879 873 } 880 function _doImages_inline_callback($matches) { 881 $whole_match = $matches[1]; 882 $alt_text = $matches[2]; 883 $url = $matches[3] == '' ? $matches[4] : $matches[3]; 884 $title =& $matches[7]; 885 886 $alt_text = $this->encodeAttribute($alt_text); 887 $url = $this->encodeAttribute($url); 888 $result = "<img src=\"$url\" alt=\"$alt_text\""; 889 if (isset($title)) { 890 $title = $this->encodeAttribute($title); 891 $result .= " title=\"$title\""; # $title already quoted 874 875 function _doImages_inline_callback( $matches ) { 876 $whole_match = $matches[1]; 877 $alt_text = $matches[2]; 878 $url = $matches[3] == '' ? $matches[4] : $matches[3]; 879 $title =& $matches[7]; 880 881 $alt_text = $this->encodeAttribute( $alt_text ); 882 $url = $this->encodeAttribute( $url ); 883 $result = "<img src=\"$url\" alt=\"$alt_text\""; 884 if ( isset( $title ) ) { 885 $title = $this->encodeAttribute( $title ); 886 $result .= " title=\"$title\""; # $title already quoted 892 887 } 893 888 $result .= $this->empty_element_suffix; 894 889 895 return $this->hashPart($result); 896 } 897 898 899 function doHeaders($text) { 890 return $this->hashPart( $result ); 891 } 892 893 function doHeaders( $text ) { 900 894 # Setext-style headers: 901 895 # Header 1 902 896 # ======== 903 # 897 # 904 898 # Header 2 905 899 # -------- 906 900 # 907 $text = preg_replace_callback( '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',908 array( &$this, '_doHeaders_callback_setext'), $text);901 $text = preg_replace_callback( '{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx', 902 array( &$this, '_doHeaders_callback_setext' ), $text ); 909 903 910 904 # atx-style headers: … … 915 909 # ###### Header 6 916 910 # 917 $text = preg_replace_callback( '{911 $text = preg_replace_callback( '{ 918 912 ^(\#{1,6}) # $1 = string of #\'s 919 913 [ ]* … … 923 917 \n+ 924 918 }xm', 925 array( &$this, '_doHeaders_callback_atx'), $text);919 array( &$this, '_doHeaders_callback_atx' ), $text ); 926 920 927 921 return $text; 928 922 } 929 function _doHeaders_callback_setext($matches) { 923 924 function _doHeaders_callback_setext( $matches ) { 930 925 # Terrible hack to check we haven't found an empty list item. 931 if ( $matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1]))926 if ( $matches[2] == '-' && preg_match( '{^-(?: |$)}', $matches[1] ) ) { 932 927 return $matches[0]; 933 928 } 929 934 930 $level = $matches[2]{0} == '=' ? 1 : 2; 935 $block = "<h$level>".$this->runSpanGamut($matches[1])."</h$level>"; 936 return "\n" . $this->hashBlock($block) . "\n\n"; 937 } 938 function _doHeaders_callback_atx($matches) { 939 $level = strlen($matches[1]); 940 $block = "<h$level>".$this->runSpanGamut($matches[2])."</h$level>"; 941 return "\n" . $this->hashBlock($block) . "\n\n"; 942 } 943 944 945 function doLists($text) { 946 # 947 # Form HTML ordered (numbered) and unordered (bulleted) lists. 948 # 931 $block = "<h$level>" . $this->runSpanGamut( $matches[1] ) . "</h$level>"; 932 933 return "\n" . $this->hashBlock( $block ) . "\n\n"; 934 } 935 936 function _doHeaders_callback_atx( $matches ) { 937 $level = strlen( $matches[1] ); 938 $block = "<h$level>" . $this->runSpanGamut( $matches[2] ) . "</h$level>"; 939 940 return "\n" . $this->hashBlock( $block ) . "\n\n"; 941 } 942 943 function doLists( $text ) { 944 # 945 # Form HTML ordered (numbered) and unordered (bulleted) lists. 946 # 949 947 $less_than_tab = $this->tab_width - 1; 950 948 … … 957 955 $marker_ul_re => $marker_ol_re, 958 956 $marker_ol_re => $marker_ul_re, 959 960 961 foreach ( $markers_relist as $marker_re => $other_marker_re) {957 ); 958 959 foreach ( $markers_relist as $marker_re => $other_marker_re ) { 962 960 # Re-usable pattern to match any entirel ul or ol list: 963 961 $whole_list_re = ' 964 962 ( # $1 = whole list 965 963 ( # $2 966 ([ ]{0,' .$less_than_tab.'}) # $3 = number of spaces967 (' .$marker_re.') # $4 = first list item marker964 ([ ]{0,' . $less_than_tab . '}) # $3 = number of spaces 965 (' . $marker_re . ') # $4 = first list item marker 968 966 [ ]+ 969 967 ) … … 976 974 (?! # Negative lookahead for another list item marker 977 975 [ ]* 978 ' .$marker_re.'[ ]+976 ' . $marker_re . '[ ]+ 979 977 ) 980 978 | … … 982 980 \n 983 981 \3 # Must have the same indentation 984 ' .$other_marker_re.'[ ]+982 ' . $other_marker_re . '[ ]+ 985 983 ) 986 984 ) 987 985 ) 988 986 '; // mx 989 987 990 988 # We use a different prefix before nested lists than top-level lists. 991 989 # See extended comment in _ProcessListItems(). 992 993 if ( $this->list_level) {994 $text = preg_replace_callback( '{990 991 if ( $this->list_level ) { 992 $text = preg_replace_callback( '{ 995 993 ^ 996 ' .$whole_list_re.'994 ' . $whole_list_re . ' 997 995 }mx', 998 array(&$this, '_doLists_callback'), $text); 999 } 1000 else { 1001 $text = preg_replace_callback('{ 996 array( &$this, '_doLists_callback' ), $text ); 997 } else { 998 $text = preg_replace_callback( '{ 1002 999 (?:(?<=\n)\n|\A\n?) # Must eat the newline 1003 ' .$whole_list_re.'1000 ' . $whole_list_re . ' 1004 1001 }mx', 1005 array( &$this, '_doLists_callback'), $text);1002 array( &$this, '_doLists_callback' ), $text ); 1006 1003 } 1007 1004 } … … 1009 1006 return $text; 1010 1007 } 1011 function _doLists_callback($matches) { 1008 1009 function _doLists_callback( $matches ) { 1012 1010 # Re-usable patterns to match list item bullets and number markers: 1013 1011 $marker_ul_re = '[*+-]'; 1014 1012 $marker_ol_re = '\d+[.]'; 1015 1013 $marker_any_re = "(?:$marker_ul_re|$marker_ol_re)"; 1016 1017 $list = $matches[1];1018 $list_type = preg_match( "/$marker_ul_re/", $matches[4]) ? "ul" : "ol";1019 1014 1015 $list = $matches[1]; 1016 $list_type = preg_match( "/$marker_ul_re/", $matches[4] ) ? "ul" : "ol"; 1017 1020 1018 $marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re ); 1021 1019 1022 1020 $list .= "\n"; 1023 $result = $this->processListItems($list, $marker_any_re); 1024 1025 $result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>"); 1026 return "\n". $result ."\n\n"; 1021 $result = $this->processListItems( $list, $marker_any_re ); 1022 1023 $result = $this->hashBlock( "<$list_type>\n" . $result . "</$list_type>" ); 1024 1025 return "\n" . $result . "\n\n"; 1027 1026 } 1028 1027 1029 1028 var $list_level = 0; 1030 1029 1031 function processListItems( $list_str, $marker_any_re) {1032 #1033 # Process the contents of a single ordered or unordered list, splitting it1034 # into individual list items.1035 #1030 function processListItems( $list_str, $marker_any_re ) { 1031 # 1032 # Process the contents of a single ordered or unordered list, splitting it 1033 # into individual list items. 1034 # 1036 1035 # The $this->list_level global keeps track of when we're inside a list. 1037 1036 # Each time we enter a list, we increment it; when we leave a list, … … 1054 1053 # change the syntax rules such that sub-lists must start with a 1055 1054 # starting cardinal number; e.g. "1." or "a.". 1056 1057 $this->list_level ++;1055 1056 $this->list_level ++; 1058 1057 1059 1058 # trim trailing blank lines: 1060 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str);1061 1062 $list_str = preg_replace_callback( '{1059 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str ); 1060 1061 $list_str = preg_replace_callback( '{ 1063 1062 (\n)? # leading line = $1 1064 1063 (^[ ]*) # leading whitespace = $2 1065 (' .$marker_any_re.' # list marker and space = $31064 (' . $marker_any_re . ' # list marker and space = $3 1066 1065 (?:[ ]+|(?=\n)) # space only required if item is not empty 1067 1066 ) 1068 1067 ((?s:.*?)) # list item text = $4 1069 1068 (?:(\n+(?=\n))|\n) # tailing blank line = $5 1070 (?= \n* (\z | \2 (' .$marker_any_re.') (?:[ ]+|(?=\n))))1069 (?= \n* (\z | \2 (' . $marker_any_re . ') (?:[ ]+|(?=\n)))) 1071 1070 }xm', 1072 array(&$this, '_processListItems_callback'), $list_str); 1073 1074 $this->list_level--; 1071 array( &$this, '_processListItems_callback' ), $list_str ); 1072 1073 $this->list_level --; 1074 1075 1075 return $list_str; 1076 1076 } 1077 function _processListItems_callback($matches) { 1078 $item = $matches[4]; 1079 $leading_line =& $matches[1]; 1080 $leading_space =& $matches[2]; 1081 $marker_space = $matches[3]; 1077 1078 function _processListItems_callback( $matches ) { 1079 $item = $matches[4]; 1080 $leading_line =& $matches[1]; 1081 $leading_space =& $matches[2]; 1082 $marker_space = $matches[3]; 1082 1083 $tailing_blank_line =& $matches[5]; 1083 1084 1084 if ( $leading_line || $tailing_blank_line ||1085 preg_match('/\n{2,}/', $item))1086 {1085 if ( $leading_line || $tailing_blank_line || 1086 preg_match( '/\n{2,}/', $item ) 1087 ) { 1087 1088 # Replace marker with the appropriate whitespace indentation 1088 $item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item; 1089 $item = $this->runBlockGamut($this->outdent($item)."\n"); 1090 } 1091 else { 1089 $item = $leading_space . str_repeat( ' ', strlen( $marker_space ) ) . $item; 1090 $item = $this->runBlockGamut( $this->outdent( $item ) . "\n" ); 1091 } else { 1092 1092 # Recursion for sub-lists: 1093 $item = $this->doLists( $this->outdent($item));1094 $item = preg_replace( '/\n+$/', '', $item);1095 $item = $this->runSpanGamut( $item);1093 $item = $this->doLists( $this->outdent( $item ) ); 1094 $item = preg_replace( '/\n+$/', '', $item ); 1095 $item = $this->runSpanGamut( $item ); 1096 1096 } 1097 1097 … … 1099 1099 } 1100 1100 1101 1102 function doCodeBlocks($text) { 1103 # 1104 # Process Markdown `<pre><code>` blocks. 1105 # 1106 $text = preg_replace_callback('{ 1101 function doCodeBlocks( $text ) { 1102 # 1103 # Process Markdown `<pre><code>` blocks. 1104 # 1105 $text = preg_replace_callback( '{ 1107 1106 (?:\n\n|\A\n?) 1108 1107 ( # $1 = the code block -- one or more lines, starting with a space/tab 1109 1108 (?> 1110 [ ]{' .$this->tab_width.'} # Lines must start with a tab or a tab-width of spaces1109 [ ]{' . $this->tab_width . '} # Lines must start with a tab or a tab-width of spaces 1111 1110 .*\n+ 1112 1111 )+ 1113 1112 ) 1114 ((?=^[ ]{0,' .$this->tab_width.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc1113 ((?=^[ ]{0,' . $this->tab_width . '}\S)|\Z) # Lookahead for non-space at line-start, or end of doc 1115 1114 }xm', 1116 array( &$this, '_doCodeBlocks_callback'), $text);1115 array( &$this, '_doCodeBlocks_callback' ), $text ); 1117 1116 1118 1117 return $text; 1119 1118 } 1120 function _doCodeBlocks_callback($matches) { 1119 1120 function _doCodeBlocks_callback( $matches ) { 1121 1121 $codeblock = $matches[1]; 1122 1122 1123 $codeblock = $this->outdent( $codeblock);1124 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES);1123 $codeblock = $this->outdent( $codeblock ); 1124 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES ); 1125 1125 1126 1126 # trim leading newlines and trailing newlines 1127 $codeblock = preg_replace( '/\A\n+|\n+\z/', '', $codeblock);1127 $codeblock = preg_replace( '/\A\n+|\n+\z/', '', $codeblock ); 1128 1128 1129 1129 $codeblock = "<pre><code>$codeblock\n</code></pre>"; 1130 return "\n\n".$this->hashBlock($codeblock)."\n\n"; 1131 }1132 1133 1134 function makeCodeSpan( $code) {1135 #1136 # Create a code span markup for $code. Called from handleSpanToken.1137 #1138 $code = htmlspecialchars( trim($code), ENT_NOQUOTES);1139 return $this->hashPart("<code>$code</code>"); 1140 }1141 1130 1131 return "\n\n" . $this->hashBlock( $codeblock ) . "\n\n"; 1132 } 1133 1134 function makeCodeSpan( $code ) { 1135 # 1136 # Create a code span markup for $code. Called from handleSpanToken. 1137 # 1138 $code = htmlspecialchars( trim( $code ), ENT_NOQUOTES ); 1139 1140 return $this->hashPart( "<code>$code</code>" ); 1141 } 1142 1142 1143 1143 var $em_relist = array( … … 1145 1145 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', 1146 1146 '_' => '(?<=\S|^)(?<!_)_(?!_)', 1147 1147 ); 1148 1148 var $strong_relist = array( 1149 1149 '' => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?=\S|$)(?![.,:;]\s)', 1150 1150 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', 1151 1151 '__' => '(?<=\S|^)(?<!_)__(?!_)', 1152 1152 ); 1153 1153 var $em_strong_relist = array( 1154 1154 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?=\S|$)(?![.,:;]\s)', 1155 1155 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', 1156 1156 '___' => '(?<=\S|^)(?<!_)___(?!_)', 1157 1157 ); 1158 1158 var $em_strong_prepared_relist; 1159 1159 1160 1160 function prepareItalicsAndBold() { 1161 #1162 # Prepare regular expressions for searching emphasis tokens in any1163 # context.1164 #1165 foreach ( $this->em_relist as $em => $em_re) {1166 foreach ( $this->strong_relist as $strong => $strong_re) {1161 # 1162 # Prepare regular expressions for searching emphasis tokens in any 1163 # context. 1164 # 1165 foreach ( $this->em_relist as $em => $em_re ) { 1166 foreach ( $this->strong_relist as $strong => $strong_re ) { 1167 1167 # Construct list of allowed token expressions. 1168 1168 $token_relist = array(); 1169 if ( isset($this->em_strong_relist["$em$strong"])) {1169 if ( isset( $this->em_strong_relist["$em$strong"] ) ) { 1170 1170 $token_relist[] = $this->em_strong_relist["$em$strong"]; 1171 1171 } 1172 1172 $token_relist[] = $em_re; 1173 1173 $token_relist[] = $strong_re; 1174 1174 1175 1175 # Construct master expression from list. 1176 $token_re = '{('. implode('|', $token_relist) .')}';1176 $token_re = '{(' . implode( '|', $token_relist ) . ')}'; 1177 1177 $this->em_strong_prepared_relist["$em$strong"] = $token_re; 1178 1178 } 1179 1179 } 1180 1180 } 1181 1182 function doItalicsAndBold( $text) {1183 $token_stack = array('');1184 $text_stack = array('');1185 $em = '';1186 $strong = '';1181 1182 function doItalicsAndBold( $text ) { 1183 $token_stack = array( '' ); 1184 $text_stack = array( '' ); 1185 $em = ''; 1186 $strong = ''; 1187 1187 $tree_char_em = false; 1188 1189 while ( 1) {1188 1189 while ( 1 ) { 1190 1190 # 1191 1191 # Get prepared regular expression for seraching emphasis tokens … … 1193 1193 # 1194 1194 $token_re = $this->em_strong_prepared_relist["$em$strong"]; 1195 1195 1196 1196 # 1197 # Each loop iteration search for the next emphasis token. 1197 # Each loop iteration search for the next emphasis token. 1198 1198 # Each token is then passed to handleSpanToken. 1199 1199 # 1200 $parts = preg_split( $token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);1200 $parts = preg_split( $token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE ); 1201 1201 $text_stack[0] .= $parts[0]; 1202 1202 $token =& $parts[1]; 1203 $text =& $parts[2];1204 1205 if ( empty($token)) {1203 $text =& $parts[2]; 1204 1205 if ( empty( $token ) ) { 1206 1206 # Reached end of text span: empty stack without emitting. 1207 1207 # any more emphasis. 1208 while ( $token_stack[0]) {1209 $text_stack[1] .= array_shift( $token_stack);1210 $text_stack[0] .= array_shift( $text_stack);1208 while ( $token_stack[0] ) { 1209 $text_stack[1] .= array_shift( $token_stack ); 1210 $text_stack[0] .= array_shift( $text_stack ); 1211 1211 } 1212 1212 break; 1213 1213 } 1214 1215 $token_len = strlen( $token);1216 if ( $tree_char_em) {1214 1215 $token_len = strlen( $token ); 1216 if ( $tree_char_em ) { 1217 1217 # Reached closing marker while inside a three-char emphasis. 1218 if ( $token_len == 3) {1218 if ( $token_len == 3 ) { 1219 1219 # Three-char closing marker, close em and strong. 1220 array_shift( $token_stack);1221 $span = array_shift( $text_stack);1222 $span = $this->runSpanGamut( $span);1220 array_shift( $token_stack ); 1221 $span = array_shift( $text_stack ); 1222 $span = $this->runSpanGamut( $span ); 1223 1223 $span = "<strong><em>$span</em></strong>"; 1224 $text_stack[0] .= $this->hashPart( $span);1225 $em = '';1224 $text_stack[0] .= $this->hashPart( $span ); 1225 $em = ''; 1226 1226 $strong = ''; 1227 1227 } else { 1228 1228 # Other closing marker: close one em or strong and 1229 1229 # change current token state to match the other 1230 $token_stack[0] = str_repeat( $token{0}, 3-$token_len);1231 $tag = $token_len == 2 ? "strong" : "em";1232 $span = $text_stack[0];1233 $span = $this->runSpanGamut($span);1234 $span = "<$tag>$span</$tag>";1235 $text_stack[0] = $this->hashPart($span);1236 $$tag = ''; # $$tag stands for $em or $strong1230 $token_stack[0] = str_repeat( $token{0}, 3 - $token_len ); 1231 $tag = $token_len == 2 ? "strong" : "em"; 1232 $span = $text_stack[0]; 1233 $span = $this->runSpanGamut( $span ); 1234 $span = "<$tag>$span</$tag>"; 1235 $text_stack[0] = $this->hashPart( $span ); 1236 $$tag = ''; # $$tag stands for $em or $strong 1237 1237 } 1238 1238 $tree_char_em = false; 1239 } else if ( $token_len == 3) {1240 if ( $em) {1239 } else if ( $token_len == 3 ) { 1240 if ( $em ) { 1241 1241 # Reached closing marker for both em and strong. 1242 1242 # Closing strong marker: 1243 for ( $i = 0; $i < 2; ++$i) {1244 $shifted_token = array_shift( $token_stack);1245 $tag = strlen($shifted_token) == 2 ? "strong" : "em";1246 $span = array_shift($text_stack);1247 $span = $this->runSpanGamut($span);1248 $span = "<$tag>$span</$tag>";1249 $text_stack[0] .= $this->hashPart( $span);1243 for ( $i = 0; $i < 2; ++ $i ) { 1244 $shifted_token = array_shift( $token_stack ); 1245 $tag = strlen( $shifted_token ) == 2 ? "strong" : "em"; 1246 $span = array_shift( $text_stack ); 1247 $span = $this->runSpanGamut( $span ); 1248 $span = "<$tag>$span</$tag>"; 1249 $text_stack[0] .= $this->hashPart( $span ); 1250 1250 $$tag = ''; # $$tag stands for $em or $strong 1251 1251 } 1252 1252 } else { 1253 # Reached opening three-char emphasis marker. Push on token 1253 # Reached opening three-char emphasis marker. Push on token 1254 1254 # stack; will be handled by the special condition above. 1255 $em = $token{0};1255 $em = $token{0}; 1256 1256 $strong = "$em$em"; 1257 array_unshift( $token_stack, $token);1258 array_unshift( $text_stack, '');1257 array_unshift( $token_stack, $token ); 1258 array_unshift( $text_stack, '' ); 1259 1259 $tree_char_em = true; 1260 1260 } 1261 } else if ( $token_len == 2) {1262 if ( $strong) {1261 } else if ( $token_len == 2 ) { 1262 if ( $strong ) { 1263 1263 # Unwind any dangling emphasis marker: 1264 if ( strlen($token_stack[0]) == 1) {1265 $text_stack[1] .= array_shift( $token_stack);1266 $text_stack[0] .= array_shift( $text_stack);1264 if ( strlen( $token_stack[0] ) == 1 ) { 1265 $text_stack[1] .= array_shift( $token_stack ); 1266 $text_stack[0] .= array_shift( $text_stack ); 1267 1267 } 1268 1268 # Closing strong marker: 1269 array_shift( $token_stack);1270 $span = array_shift( $text_stack);1271 $span = $this->runSpanGamut( $span);1269 array_shift( $token_stack ); 1270 $span = array_shift( $text_stack ); 1271 $span = $this->runSpanGamut( $span ); 1272 1272 $span = "<strong>$span</strong>"; 1273 $text_stack[0] .= $this->hashPart( $span);1273 $text_stack[0] .= $this->hashPart( $span ); 1274 1274 $strong = ''; 1275 1275 } else { 1276 array_unshift( $token_stack, $token);1277 array_unshift( $text_stack, '');1276 array_unshift( $token_stack, $token ); 1277 array_unshift( $text_stack, '' ); 1278 1278 $strong = $token; 1279 1279 } 1280 1280 } else { 1281 1281 # Here $token_len == 1 1282 if ( $em) {1283 if ( strlen($token_stack[0]) == 1) {1282 if ( $em ) { 1283 if ( strlen( $token_stack[0] ) == 1 ) { 1284 1284 # Closing emphasis marker: 1285 array_shift( $token_stack);1286 $span = array_shift( $text_stack);1287 $span = $this->runSpanGamut( $span);1285 array_shift( $token_stack ); 1286 $span = array_shift( $text_stack ); 1287 $span = $this->runSpanGamut( $span ); 1288 1288 $span = "<em>$span</em>"; 1289 $text_stack[0] .= $this->hashPart( $span);1289 $text_stack[0] .= $this->hashPart( $span ); 1290 1290 $em = ''; 1291 1291 } else { … … 1293 1293 } 1294 1294 } else { 1295 array_unshift( $token_stack, $token);1296 array_unshift( $text_stack, '');1295 array_unshift( $token_stack, $token ); 1296 array_unshift( $text_stack, '' ); 1297 1297 $em = $token; 1298 1298 } 1299 1299 } 1300 1300 } 1301 1301 1302 return $text_stack[0]; 1302 1303 } 1303 1304 1304 1305 function doBlockQuotes($text) { 1306 $text = preg_replace_callback('/ 1305 function doBlockQuotes( $text ) { 1306 $text = preg_replace_callback( '/ 1307 1307 ( # Wrap whole match in $1 1308 1308 (?> … … 1314 1314 ) 1315 1315 /xm', 1316 array( &$this, '_doBlockQuotes_callback'), $text);1316 array( &$this, '_doBlockQuotes_callback' ), $text ); 1317 1317 1318 1318 return $text; 1319 1319 } 1320 function _doBlockQuotes_callback($matches) { 1320 1321 function _doBlockQuotes_callback( $matches ) { 1321 1322 $bq = $matches[1]; 1322 1323 # trim one level of quoting - trim whitespace-only lines 1323 $bq = preg_replace( '/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);1324 $bq = $this->runBlockGamut( $bq);# recurse1325 1326 $bq = preg_replace( '/^/m', " ", $bq);1327 # These leading spaces cause problem with <pre> content, 1324 $bq = preg_replace( '/^[ ]*>[ ]?|^[ ]+$/m', '', $bq ); 1325 $bq = $this->runBlockGamut( $bq ); # recurse 1326 1327 $bq = preg_replace( '/^/m', " ", $bq ); 1328 # These leading spaces cause problem with <pre> content, 1328 1329 # so we need to fix that: 1329 $bq = preg_replace_callback('{(\s*<pre>.+?</pre>)}sx', 1330 array(&$this, '_doBlockQuotes_callback2'), $bq); 1331 1332 return "\n". $this->hashBlock("<blockquote>\n$bq\n</blockquote>")."\n\n"; 1333 } 1334 function _doBlockQuotes_callback2($matches) { 1330 $bq = preg_replace_callback( '{(\s*<pre>.+?</pre>)}sx', 1331 array( &$this, '_doBlockQuotes_callback2' ), $bq ); 1332 1333 return "\n" . $this->hashBlock( "<blockquote>\n$bq\n</blockquote>" ) . "\n\n"; 1334 } 1335 1336 function _doBlockQuotes_callback2( $matches ) { 1335 1337 $pre = $matches[1]; 1336 $pre = preg_replace('/^ /m', '', $pre); 1338 $pre = preg_replace( '/^ /m', '', $pre ); 1339 1337 1340 return $pre; 1338 1341 } 1339 1342 1340 1341 function formParagraphs($text) { 1342 # 1343 # Params: 1344 # $text - string to process with html <p> tags 1345 # 1343 function formParagraphs( $text ) { 1344 # 1345 # Params: 1346 # $text - string to process with html <p> tags 1347 # 1346 1348 # Strip leading and trailing lines: 1347 $text = preg_replace( '/\A\n+|\n+\z/', '', $text);1348 1349 $grafs = preg_split( '/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);1349 $text = preg_replace( '/\A\n+|\n+\z/', '', $text ); 1350 1351 $grafs = preg_split( '/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY ); 1350 1352 1351 1353 # 1352 1354 # Wrap <p> tags and unhashify HTML blocks 1353 1355 # 1354 foreach ( $grafs as $key => $value) {1355 if ( !preg_match('/^B\x1A[0-9]+B$/', $value)) {1356 foreach ( $grafs as $key => $value ) { 1357 if ( ! preg_match( '/^B\x1A[0-9]+B$/', $value ) ) { 1356 1358 # Is a paragraph. 1357 $value = $this->runSpanGamut( $value);1358 $value = preg_replace( '/^([ ]*)/', "<p>", $value);1359 $value = $this->runSpanGamut( $value ); 1360 $value = preg_replace( '/^([ ]*)/', "<p>", $value ); 1359 1361 $value .= "</p>"; 1360 $grafs[$key] = $this->unhash($value); 1361 } 1362 else { 1362 $grafs[ $key ] = $this->unhash( $value ); 1363 } else { 1363 1364 # Is a block. 1364 1365 # Modify elements of @grafs in-place... 1365 $graf = $value;1366 $block = $this->html_hashes[ $graf];1367 $graf = $block;1366 $graf = $value; 1367 $block = $this->html_hashes[ $graf ]; 1368 $graf = $block; 1368 1369 // if (preg_match('{ 1369 1370 // \A … … 1390 1391 // # that initialization code should be pulled into its own sub, though. 1391 1392 // $div_content = $this->hashHTMLBlocks($div_content); 1392 // 1393 // 1393 1394 // # Run document gamut methods on the content. 1394 1395 // foreach ($this->document_gamut as $method => $priority) { … … 1401 1402 // $graf = $div_open . "\n" . $div_content . "\n" . $div_close; 1402 1403 // } 1403 $grafs[ $key] = $graf;1404 } 1405 } 1406 1407 return implode( "\n\n", $grafs);1408 } 1409 1410 1411 function encodeAttribute($text) {1412 #1413 # Encode text for a double-quoted HTML attribute. This function1414 # is *not* suitable for attributes enclosed in single quotes.1415 #1416 $text = $this->encodeAmpsAndAngles($text);1417 $text = str_replace('"', '"', $text); 1404 $grafs[ $key ] = $graf; 1405 } 1406 } 1407 1408 return implode( "\n\n", $grafs ); 1409 } 1410 1411 function encodeAttribute( $text ) { 1412 # 1413 # Encode text for a double-quoted HTML attribute. This function 1414 # is *not* suitable for attributes enclosed in single quotes. 1415 # 1416 $text = $this->encodeAmpsAndAngles( $text ); 1417 $text = str_replace( '"', '"', $text ); 1418 1418 1419 return $text; 1419 1420 } 1420 1421 1422 function encodeAmpsAndAngles($text) { 1423 # 1424 # Smart processing for ampersands and angle brackets that need to 1425 # be encoded. Valid character entities are left alone unless the 1426 # no-entities mode is set. 1427 # 1428 if ($this->no_entities) { 1429 $text = str_replace('&', '&', $text); 1421 1422 function encodeAmpsAndAngles( $text ) { 1423 # 1424 # Smart processing for ampersands and angle brackets that need to 1425 # be encoded. Valid character entities are left alone unless the 1426 # no-entities mode is set. 1427 # 1428 if ( $this->no_entities ) { 1429 $text = str_replace( '&', '&', $text ); 1430 1430 } else { 1431 1431 # Ampersand-encoding based entirely on Nat Irons's Amputator 1432 1432 # MT plugin: <http://bumppo.net/projects/amputator/> 1433 $text = preg_replace( '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',1434 '&', $text);;1433 $text = preg_replace( '/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/', 1434 '&', $text );; 1435 1435 } 1436 1436 # Encode remaining <'s 1437 $text = str_replace( '<', '<', $text);1437 $text = str_replace( '<', '<', $text ); 1438 1438 1439 1439 return $text; 1440 1440 } 1441 1441 1442 1443 function doAutoLinks($text) { 1444 $text = preg_replace_callback('{<((https?|ftp|dict):[^\'">\s]+)>}i', 1445 array(&$this, '_doAutoLinks_url_callback'), $text); 1442 function doAutoLinks( $text ) { 1443 $text = preg_replace_callback( '{<((https?|ftp|dict):[^\'">\s]+)>}i', 1444 array( &$this, '_doAutoLinks_url_callback' ), $text ); 1446 1445 1447 1446 # Email addresses: <address@domain.foo> 1448 $text = preg_replace_callback( '{1447 $text = preg_replace_callback( '{ 1449 1448 < 1450 1449 (?:mailto:)? … … 1464 1463 > 1465 1464 }xi', 1466 array( &$this, '_doAutoLinks_email_callback'), $text);1465 array( &$this, '_doAutoLinks_email_callback' ), $text ); 1467 1466 1468 1467 return $text; 1469 1468 } 1470 function _doAutoLinks_url_callback($matches) { 1471 $url = $this->encodeAttribute($matches[1]); 1469 1470 function _doAutoLinks_url_callback( $matches ) { 1471 $url = $this->encodeAttribute( $matches[1] ); 1472 1472 $link = "<a href=\"$url\">$url</a>"; 1473 return $this->hashPart($link); 1474 } 1475 function _doAutoLinks_email_callback($matches) { 1473 1474 return $this->hashPart( $link ); 1475 } 1476 1477 function _doAutoLinks_email_callback( $matches ) { 1476 1478 $address = $matches[1]; 1477 $link = $this->encodeEmailAddress($address);1478 return $this->hashPart($link); 1479 }1480 1481 1482 function encodeEmailAddress( $addr) {1483 #1484 # Input: an email address, e.g. "foo@example.com"1485 #1486 # Output: the email address as a mailto link, with each character1487 # of the address encoded as either a decimal or hex entity, in1488 # the hopes of foiling most address harvesting spam bots. E.g.:1489 #1490 # <p><a href="mailto:foo1491 # @example.co1492 # m">foo@exampl1493 # e.com</a></p>1494 #1495 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk.1496 # With some optimizations by Milian Wolff.1497 #1498 $addr = "mailto:" . $addr;1499 $chars = preg_split( '/(?<!^)(?!$)/', $addr);1500 $seed = (int)abs(crc32($addr) / strlen($addr)); # Deterministic seed.1501 1502 foreach ( $chars as $key => $char) {1503 $ord = ord( $char);1479 $link = $this->encodeEmailAddress( $address ); 1480 1481 return $this->hashPart( $link ); 1482 } 1483 1484 function encodeEmailAddress( $addr ) { 1485 # 1486 # Input: an email address, e.g. "foo@example.com" 1487 # 1488 # Output: the email address as a mailto link, with each character 1489 # of the address encoded as either a decimal or hex entity, in 1490 # the hopes of foiling most address harvesting spam bots. E.g.: 1491 # 1492 # <p><a href="mailto:foo 1493 # @example.co 1494 # m">foo@exampl 1495 # e.com</a></p> 1496 # 1497 # Based by a filter by Matthew Wickline, posted to BBEdit-Talk. 1498 # With some optimizations by Milian Wolff. 1499 # 1500 $addr = "mailto:" . $addr; 1501 $chars = preg_split( '/(?<!^)(?!$)/', $addr ); 1502 $seed = (int) abs( crc32( $addr ) / strlen( $addr ) ); # Deterministic seed. 1503 1504 foreach ( $chars as $key => $char ) { 1505 $ord = ord( $char ); 1504 1506 # Ignore non-ascii chars. 1505 if ( $ord < 128) {1506 $r = ( $seed * (1 + $key)) % 100; # Pseudo-random function.1507 if ( $ord < 128 ) { 1508 $r = ( $seed * ( 1 + $key ) ) % 100; # Pseudo-random function. 1507 1509 # roughly 10% raw, 45% hex, 45% dec 1508 1510 # '@' *must* be encoded. I insist. 1509 if ($r > 90 && $char != '@') /* do nothing */; 1510 else if ($r < 45) $chars[$key] = '&#x'.dechex($ord).';'; 1511 else $chars[$key] = '&#'.$ord.';'; 1512 } 1513 } 1514 1515 $addr = implode('', $chars); 1516 $text = implode('', array_slice($chars, 7)); # text without `mailto:` 1511 if ( $r > 90 && $char != '@' ) /* do nothing */ { 1512 ; 1513 } else if ( $r < 45 ) { 1514 $chars[ $key ] = '&#x' . dechex( $ord ) . ';'; 1515 } else { 1516 $chars[ $key ] = '&#' . $ord . ';'; 1517 } 1518 } 1519 } 1520 1521 $addr = implode( '', $chars ); 1522 $text = implode( '', array_slice( $chars, 7 ) ); # text without `mailto:` 1517 1523 $addr = "<a href=\"$addr\">$text</a>"; 1518 1524 … … 1520 1526 } 1521 1527 1522 1523 function parseSpan($str) { 1524 # 1525 # Take the string $str and parse it into tokens, hashing embeded HTML, 1526 # escaped characters and handling code spans. 1527 # 1528 function parseSpan( $str ) { 1529 # 1530 # Take the string $str and parse it into tokens, hashing embeded HTML, 1531 # escaped characters and handling code spans. 1532 # 1528 1533 $output = ''; 1529 1534 1530 1535 $span_re = '{ 1531 1536 ( 1532 \\\\' .$this->escape_chars_re.'1537 \\\\' . $this->escape_chars_re . ' 1533 1538 | 1534 1539 (?<![`\\\\]) 1535 1540 `+ # code span marker 1536 ' .( $this->no_markup ? '' : '1541 ' . ( $this->no_markup ? '' : ' 1537 1542 | 1538 1543 <!-- .*? --> # comment … … 1546 1551 )? 1547 1552 > 1548 ' ).'1553 ' ) . ' 1549 1554 ) 1550 1555 }xs'; 1551 1556 1552 while ( 1) {1557 while ( 1 ) { 1553 1558 # 1554 # Each loop iteration seach for either the next tag, the next 1555 # openning code span marker, or the next escaped character. 1559 # Each loop iteration seach for either the next tag, the next 1560 # openning code span marker, or the next escaped character. 1556 1561 # Each token is then passed to handleSpanToken. 1557 1562 # 1558 $parts = preg_split( $span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);1559 1563 $parts = preg_split( $span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE ); 1564 1560 1565 # Create token from text preceding tag. 1561 if ( $parts[0] != "") {1566 if ( $parts[0] != "" ) { 1562 1567 $output .= $parts[0]; 1563 1568 } 1564 1569 1565 1570 # Check if we reach the end. 1566 if ( isset($parts[1])) {1567 $output .= $this->handleSpanToken( $parts[1], $parts[2]);1571 if ( isset( $parts[1] ) ) { 1572 $output .= $this->handleSpanToken( $parts[1], $parts[2] ); 1568 1573 $str = $parts[2]; 1569 } 1570 else { 1574 } else { 1571 1575 break; 1572 1576 } 1573 1577 } 1574 1578 1575 1579 return $output; 1576 1580 } 1577 1578 1579 function handleSpanToken($token, &$str) { 1580 # 1581 # Handle $token provided by parseSpan by determining its nature and 1582 # returning the corresponding value that should replace it. 1583 # 1584 switch ($token{0}) { 1581 1582 function handleSpanToken( $token, &$str ) { 1583 # 1584 # Handle $token provided by parseSpan by determining its nature and 1585 # returning the corresponding value that should replace it. 1586 # 1587 switch ( $token{0} ) { 1585 1588 case "\\": 1586 return $this->hashPart( "&#". ord($token{1}). ";");1589 return $this->hashPart( "&#" . ord( $token{1} ) . ";" ); 1587 1590 case "`": 1588 1591 # Search for end marker in remaining text. 1589 if ( preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',1590 $str, $matches ))1591 {1592 $ str = $matches[2];1593 $codespan = $this->makeCodeSpan($matches[1]); 1594 return $this->hashPart( $codespan);1592 if ( preg_match( '/^(.*?[^`])' . preg_quote( $token ) . '(?!`)(.*)$/sm', 1593 $str, $matches ) ) { 1594 $str = $matches[2]; 1595 $codespan = $this->makeCodeSpan( $matches[1] ); 1596 1597 return $this->hashPart( $codespan ); 1595 1598 } 1599 1596 1600 return $token; // return as text since no ending marker found. 1597 1601 default: 1598 return $this->hashPart($token); 1599 } 1600 } 1601 1602 1603 function outdent($text) { 1604 # 1605 # Remove one level of line-leading tabs or spaces 1606 # 1607 return preg_replace('/^(\t|[ ]{1,'.$this->tab_width.'})/m', '', $text); 1608 } 1609 1610 1611 # String length function for detab. `_initDetab` will create a function to 1602 return $this->hashPart( $token ); 1603 } 1604 } 1605 1606 function outdent( $text ) { 1607 # 1608 # Remove one level of line-leading tabs or spaces 1609 # 1610 return preg_replace( '/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text ); 1611 } 1612 1613 1614 # String length function for detab. `_initDetab` will create a function to 1612 1615 # hanlde UTF-8 if the default function does not exist. 1613 1616 var $utf8_strlen = 'mb_strlen'; 1614 1615 function detab( $text) {1616 #1617 # Replace tabs with the appropriate amount of space.1618 #1617 1618 function detab( $text ) { 1619 # 1620 # Replace tabs with the appropriate amount of space. 1621 # 1619 1622 # For each line we separate the line in blocks delemited by 1620 # tab characters. Then we reconstruct every line by adding the 1623 # tab characters. Then we reconstruct every line by adding the 1621 1624 # appropriate number of space between each blocks. 1622 1623 $text = preg_replace_callback( '/^.*\t.*$/m',1624 array( &$this, '_detab_callback'), $text);1625 1626 $text = preg_replace_callback( '/^.*\t.*$/m', 1627 array( &$this, '_detab_callback' ), $text ); 1625 1628 1626 1629 return $text; 1627 1630 } 1628 function _detab_callback($matches) { 1629 $line = $matches[0]; 1631 1632 function _detab_callback( $matches ) { 1633 $line = $matches[0]; 1630 1634 $strlen = $this->utf8_strlen; # strlen function for UTF-8. 1631 1635 1632 1636 # Split in blocks. 1633 $blocks = explode( "\t", $line);1637 $blocks = explode( "\t", $line ); 1634 1638 # Add each blocks to the line. 1635 1639 $line = $blocks[0]; 1636 unset( $blocks[0]); # Do not add first block twice.1637 foreach ( $blocks as $block) {1640 unset( $blocks[0] ); # Do not add first block twice. 1641 foreach ( $blocks as $block ) { 1638 1642 # Calculate amount of space, insert spaces, insert block. 1639 $amount = $this->tab_width - 1640 $strlen($line, 'UTF-8') % $this->tab_width; 1641 $line .= str_repeat(" ", $amount) . $block; 1642 } 1643 $amount = $this->tab_width - 1644 $strlen( $line, 'UTF-8' ) % $this->tab_width; 1645 $line .= str_repeat( " ", $amount ) . $block; 1646 } 1647 1643 1648 return $line; 1644 1649 } 1650 1645 1651 function _initDetab() { 1646 # 1647 # Check for the availability of the function in the `utf8_strlen` property 1648 # (initially `mb_strlen`). If the function is not available, create a 1649 # function that will loosely count the number of UTF-8 characters with a 1650 # regular expression. 1651 # 1652 if (function_exists($this->utf8_strlen)) return; 1653 $this->utf8_strlen = create_function('$text', 'return preg_match_all( 1654 "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 1655 $text, $m);'); 1656 } 1657 1658 1659 function unhash($text) { 1660 # 1661 # Swap back in all the tags hashed by _HashHTMLBlocks. 1662 # 1663 return preg_replace_callback('/(.)\x1A[0-9]+\1/', 1664 array(&$this, '_unhash_callback'), $text); 1665 } 1666 function _unhash_callback($matches) { 1667 return $this->html_hashes[$matches[0]]; 1668 } 1669 1652 # 1653 # Check for the availability of the function in the `utf8_strlen` property 1654 # (initially `mb_strlen`). If the function is not available, create a 1655 # function that will loosely count the number of UTF-8 characters with a 1656 # regular expression. 1657 # 1658 if ( function_exists( $this->utf8_strlen ) ) { 1659 return; 1660 } 1661 $this->utf8_strlen = create_function( '$text', 'return preg_match_all( 1662 "/[\\\\x00-\\\\xBF]|[\\\\xC0-\\\\xFF][\\\\x80-\\\\xBF]*/", 1663 $text, $m);' ); 1664 } 1665 1666 function unhash( $text ) { 1667 # 1668 # Swap back in all the tags hashed by _HashHTMLBlocks. 1669 # 1670 return preg_replace_callback( '/(.)\x1A[0-9]+\1/', 1671 array( &$this, '_unhash_callback' ), $text ); 1672 } 1673 1674 function _unhash_callback( $matches ) { 1675 return $this->html_hashes[ $matches[0] ]; 1676 } 1670 1677 } 1671 1672 1678 1673 1679 # … … 1676 1682 1677 1683 class MarkdownExtra_Parser extends Markdown_Parser { 1678 1679 1684 # Prefix for footnote ids. 1680 1685 var $fn_id_prefix = ""; 1681 1682 1686 # Optional title attribute for footnote links and backlinks. 1683 1687 var $fn_link_title = MARKDOWN_FN_LINK_TITLE; 1684 1688 var $fn_backlink_title = MARKDOWN_FN_BACKLINK_TITLE; 1685 1686 1689 # Optional class attribute for footnote links and backlinks. 1687 1690 var $fn_link_class = MARKDOWN_FN_LINK_CLASS; 1688 1691 var $fn_backlink_class = MARKDOWN_FN_BACKLINK_CLASS; 1689 1690 1692 # Predefined abbreviations. 1691 1693 var $predef_abbr = array(); 1692 1694 1693 1694 1695 function MarkdownExtra_Parser() { 1695 #1696 # Constructor function. Initialize the parser object.1697 #1698 # Add extra escapable characters before parent constructor 1696 # 1697 # Constructor function. Initialize the parser object. 1698 # 1699 # Add extra escapable characters before parent constructor 1699 1700 # initialize the table. 1700 1701 $this->escape_chars .= ':|'; 1701 1702 # Insert extra document, block, and span transformations. 1702 1703 # Insert extra document, block, and span transformations. 1703 1704 # Parent constructor will do the sorting. 1704 1705 $this->document_gamut += array( … … 1707 1708 "stripAbbreviations" => 25, 1708 1709 "appendFootnotes" => 50, 1709 1710 ); 1710 1711 $this->block_gamut += array( 1711 1712 "doFencedCodeBlocks" => 5, 1712 1713 "doTables" => 15, 1713 1714 "doDefLists" => 45, 1714 1715 ); 1715 1716 $this->span_gamut += array( 1716 "doFootnotes" 1717 "doAbbreviations" 1718 1719 1717 "doFootnotes" => 5, 1718 "doAbbreviations" => 70, 1719 ); 1720 1720 1721 parent::Markdown_Parser(); 1721 1722 } 1722 1723 1723 1724 1724 # Extra variables used during extra transformations. 1725 1725 var $footnotes = array(); … … 1727 1727 var $abbr_desciptions = array(); 1728 1728 var $abbr_word_re = ''; 1729 1730 1729 # Give the current footnote number. 1731 1730 var $footnote_counter = 1; 1732 1733 1731 1734 1732 function setup() { 1735 #1736 # Setting up Extra-specific variables.1737 #1733 # 1734 # Setting up Extra-specific variables. 1735 # 1738 1736 parent::setup(); 1739 1740 $this->footnotes = array();1737 1738 $this->footnotes = array(); 1741 1739 $this->footnotes_ordered = array(); 1742 $this->abbr_desciptions = array();1743 $this->abbr_word_re = '';1744 $this->footnote_counter = 1;1745 1746 foreach ( $this->predef_abbr as $abbr_word => $abbr_desc) {1747 if ( $this->abbr_word_re)1740 $this->abbr_desciptions = array(); 1741 $this->abbr_word_re = ''; 1742 $this->footnote_counter = 1; 1743 1744 foreach ( $this->predef_abbr as $abbr_word => $abbr_desc ) { 1745 if ( $this->abbr_word_re ) { 1748 1746 $this->abbr_word_re .= '|'; 1749 $this->abbr_word_re .= preg_quote($abbr_word); 1750 $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); 1751 } 1752 } 1753 1747 } 1748 $this->abbr_word_re .= preg_quote( $abbr_word ); 1749 $this->abbr_desciptions[ $abbr_word ] = trim( $abbr_desc ); 1750 } 1751 } 1752 1754 1753 function teardown() { 1755 #1756 # Clearing Extra-specific variables.1757 #1758 $this->footnotes = array();1754 # 1755 # Clearing Extra-specific variables. 1756 # 1757 $this->footnotes = array(); 1759 1758 $this->footnotes_ordered = array(); 1760 $this->abbr_desciptions = array();1761 $this->abbr_word_re = '';1762 1759 $this->abbr_desciptions = array(); 1760 $this->abbr_word_re = ''; 1761 1763 1762 parent::teardown(); 1764 1763 } 1765 1766 1764 1765 1767 1766 ### HTML Block Parser ### 1768 1767 1769 1768 # Tags that are always treated as block tags: 1770 1769 var $block_tags_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|form|fieldset|iframe|hr|legend'; 1771 1772 1770 # Tags treated as block tags only if the opening tag is alone on it's line: 1773 1771 var $context_block_tags_re = 'script|noscript|math|ins|del'; 1774 1775 1772 # Tags where markdown="1" default to span mode: 1776 1773 var $contain_span_tags_re = 'p|h[1-6]|li|dd|dt|td|th|legend|address'; 1777 1778 # Tags which must not have their contents modified, no matter where 1774 # Tags which must not have their contents modified, no matter where 1779 1775 # they appear: 1780 1776 var $clean_tags_re = 'script|math'; 1781 1782 1777 # Tags that do not need to be closed. 1783 1778 var $auto_close_tags_re = 'hr|img'; 1784 1785 1786 function hashHTMLBlocks($text) { 1787 # 1788 # Hashify HTML Blocks and "clean tags". 1789 # 1790 # We only want to do this for block-level HTML tags, such as headers, 1791 # lists, and tables. That's because we still want to wrap <p>s around 1792 # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 1793 # phrase emphasis, and spans. The list of tags we're looking for is 1794 # hard-coded. 1795 # 1796 # This works by calling _HashHTMLBlocks_InMarkdown, which then calls 1797 # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 1798 # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back 1799 # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. 1800 # These two functions are calling each other. It's recursive! 1801 # 1779 1780 function hashHTMLBlocks( $text ) { 1781 # 1782 # Hashify HTML Blocks and "clean tags". 1783 # 1784 # We only want to do this for block-level HTML tags, such as headers, 1785 # lists, and tables. That's because we still want to wrap <p>s around 1786 # "paragraphs" that are wrapped in non-block-level tags, such as anchors, 1787 # phrase emphasis, and spans. The list of tags we're looking for is 1788 # hard-coded. 1789 # 1790 # This works by calling _HashHTMLBlocks_InMarkdown, which then calls 1791 # _HashHTMLBlocks_InHTML when it encounter block tags. When the markdown="1" 1792 # attribute is found whitin a tag, _HashHTMLBlocks_InHTML calls back 1793 # _HashHTMLBlocks_InMarkdown to handle the Markdown syntax within the tag. 1794 # These two functions are calling each other. It's recursive! 1795 # 1802 1796 # 1803 1797 # Call the HTML-in-Markdown hasher. 1804 1798 # 1805 list( $text, ) = $this->_hashHTMLBlocks_inMarkdown($text);1806 1799 list( $text, ) = $this->_hashHTMLBlocks_inMarkdown( $text ); 1800 1807 1801 return $text; 1808 1802 } 1809 function _hashHTMLBlocks_inMarkdown($text, $indent = 0, 1810 $enclosing_tag_re = '', $span = false) 1811 { 1812 # 1813 # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. 1814 # 1815 # * $indent is the number of space to be ignored when checking for code 1816 # blocks. This is important because if we don't take the indent into 1817 # account, something like this (which looks right) won't work as expected: 1818 # 1819 # <div> 1820 # <div markdown="1"> 1821 # Hello World. <-- Is this a Markdown code block or text? 1822 # </div> <-- Is this a Markdown code block or a real tag? 1823 # <div> 1824 # 1825 # If you don't like this, just don't indent the tag on which 1826 # you apply the markdown="1" attribute. 1827 # 1828 # * If $enclosing_tag_re is not empty, stops at the first unmatched closing 1829 # tag with that name. Nested tags supported. 1830 # 1831 # * If $span is true, text inside must treated as span. So any double 1832 # newline will be replaced by a single newline so that it does not create 1833 # paragraphs. 1834 # 1835 # Returns an array of that form: ( processed text , remaining text ) 1836 # 1837 if ($text === '') return array('', ''); 1803 1804 function _hashHTMLBlocks_inMarkdown( $text, $indent = 0, $enclosing_tag_re = '', $span = false ) { 1805 # 1806 # Parse markdown text, calling _HashHTMLBlocks_InHTML for block tags. 1807 # 1808 # * $indent is the number of space to be ignored when checking for code 1809 # blocks. This is important because if we don't take the indent into 1810 # account, something like this (which looks right) won't work as expected: 1811 # 1812 # <div> 1813 # <div markdown="1"> 1814 # Hello World. <-- Is this a Markdown code block or text? 1815 # </div> <-- Is this a Markdown code block or a real tag? 1816 # <div> 1817 # 1818 # If you don't like this, just don't indent the tag on which 1819 # you apply the markdown="1" attribute. 1820 # 1821 # * If $enclosing_tag_re is not empty, stops at the first unmatched closing 1822 # tag with that name. Nested tags supported. 1823 # 1824 # * If $span is true, text inside must treated as span. So any double 1825 # newline will be replaced by a single newline so that it does not create 1826 # paragraphs. 1827 # 1828 # Returns an array of that form: ( processed text , remaining text ) 1829 # 1830 if ( $text === '' ) { 1831 return array( '', '' ); 1832 } 1838 1833 1839 1834 # Regex to check for the presense of newlines around a block tag. 1840 1835 $newline_before_re = '/(?:^\n?|\n\n)*$/'; 1841 $newline_after_re =1836 $newline_after_re = 1842 1837 '{ 1843 1838 ^ # Start of text following the tag. … … 1845 1840 [ ]*\n # Must be followed by newline. 1846 1841 }xs'; 1847 1842 1848 1843 # Regex to match any tag. 1849 1844 $block_tag_re = … … 1852 1847 </? # Any opening or closing tag. 1853 1848 (?> # Tag name. 1854 ' .$this->block_tags_re.' |1855 ' .$this->context_block_tags_re.' |1856 ' .$this->clean_tags_re.' |1857 (?!\s)' .$enclosing_tag_re.'1849 ' . $this->block_tags_re . ' | 1850 ' . $this->context_block_tags_re . ' | 1851 ' . $this->clean_tags_re . ' | 1852 (?!\s)' . $enclosing_tag_re . ' 1858 1853 ) 1859 1854 (?: … … 1875 1870 # Code span marker 1876 1871 `+ 1877 ' . ( !$span ? ' # If not in span.1872 ' . ( ! $span ? ' # If not in span. 1878 1873 | 1879 1874 # Indented code block 1880 1875 (?: ^[ ]*\n | ^ | \n[ ]*\n ) 1881 [ ]{' .($indent+4).'}[^\n]* \n1876 [ ]{' . ( $indent + 4 ) . '}[^\n]* \n 1882 1877 (?> 1883 (?: [ ]{' .($indent+4).'}[^\n]* | [ ]* ) \n1878 (?: [ ]{' . ( $indent + 4 ) . '}[^\n]* | [ ]* ) \n 1884 1879 )* 1885 1880 | 1886 1881 # Fenced code block marker 1887 1882 (?> ^ | \n ) 1888 [ ]{' .($indent).'}~~~+[ ]*\n1889 ' : '' ) . ' # End (if not is span).1883 [ ]{' . ( $indent ) . '}~~~+[ ]*\n 1884 ' : '' ) . ' # End (if not is span). 1890 1885 ) 1891 1886 }xs'; 1892 1887 1893 1894 $depth = 0; # Current depth inside the tag tree. 1895 $parsed = ""; # Parsed text that will be returned. 1888 $depth = 0; # Current depth inside the tag tree. 1889 $parsed = ""; # Parsed text that will be returned. 1896 1890 1897 1891 # … … 1903 1897 # Split the text using the first $tag_match pattern found. 1904 1898 # Text before pattern will be first in the array, text after 1905 # pattern will be at the end, and between will be any catches made 1899 # pattern will be at the end, and between will be any catches made 1906 1900 # by the pattern. 1907 1901 # 1908 $parts = preg_split( $block_tag_re, $text, 2,1909 PREG_SPLIT_DELIM_CAPTURE);1910 1911 # If in Markdown span mode, add a empty-string span-level hash 1902 $parts = preg_split( $block_tag_re, $text, 2, 1903 PREG_SPLIT_DELIM_CAPTURE ); 1904 1905 # If in Markdown span mode, add a empty-string span-level hash 1912 1906 # after each newline to prevent triggering any block element. 1913 if ( $span) {1914 $void = $this->hashPart("", ':');1915 $newline = "$void\n";1916 $parts[0] = $void . str_replace( "\n", $newline, $parts[0]) . $void;1917 } 1918 1907 if ( $span ) { 1908 $void = $this->hashPart( "", ':' ); 1909 $newline = "$void\n"; 1910 $parts[0] = $void . str_replace( "\n", $newline, $parts[0] ) . $void; 1911 } 1912 1919 1913 $parsed .= $parts[0]; # Text before current tag. 1920 1914 1921 1915 # If end of $text has been reached. Stop loop. 1922 if ( count($parts) < 3) {1916 if ( count( $parts ) < 3 ) { 1923 1917 $text = ""; 1924 1918 break; 1925 1919 } 1926 1927 $tag = $parts[1]; # Tag to handle.1928 $text = $parts[2]; # Remaining text after current tag.1929 $tag_re = preg_quote( $tag); # For use in a regular expression.1930 1920 1921 $tag = $parts[1]; # Tag to handle. 1922 $text = $parts[2]; # Remaining text after current tag. 1923 $tag_re = preg_quote( $tag ); # For use in a regular expression. 1924 1931 1925 # 1932 1926 # Check for: Code span marker 1933 1927 # 1934 if ( $tag{0} == "`") {1928 if ( $tag{0} == "`" ) { 1935 1929 # Find corresponding end marker. 1936 $tag_re = preg_quote($tag); 1937 if (preg_match('{^(?>.+?|\n(?!\n))*?(?<!`)'.$tag_re.'(?!`)}', 1938 $text, $matches)) 1939 { 1930 $tag_re = preg_quote( $tag ); 1931 if ( preg_match( '{^(?>.+?|\n(?!\n))*?(?<!`)' . $tag_re . '(?!`)}', 1932 $text, $matches ) ) { 1940 1933 # End marker found: pass text unchanged until marker. 1941 1934 $parsed .= $tag . $matches[0]; 1942 $text = substr($text, strlen($matches[0])); 1943 } 1944 else { 1935 $text = substr( $text, strlen( $matches[0] ) ); 1936 } else { 1945 1937 # Unmatched marker: just skip it. 1946 1938 $parsed .= $tag; … … 1950 1942 # Check for: Indented code block. 1951 1943 # 1952 else if ( $tag{0} == "\n" || $tag{0} == " ") {1953 # Indented code block: pass it unchanged, will be handled 1944 else if ( $tag{0} == "\n" || $tag{0} == " " ) { 1945 # Indented code block: pass it unchanged, will be handled 1954 1946 # later. 1955 1947 $parsed .= $tag; … … 1958 1950 # Check for: Fenced code block marker. 1959 1951 # 1960 else if ( $tag{0} == "~") {1952 else if ( $tag{0} == "~" ) { 1961 1953 # Fenced code block marker: find matching end marker. 1962 $tag_re = preg_quote(trim($tag)); 1963 if (preg_match('{^(?>.*\n)+?'.$tag_re.' *\n}', $text, 1964 $matches)) 1965 { 1954 $tag_re = preg_quote( trim( $tag ) ); 1955 if ( preg_match( '{^(?>.*\n)+?' . $tag_re . ' *\n}', $text, 1956 $matches ) ) { 1966 1957 # End marker found: pass text unchanged until marker. 1967 1958 $parsed .= $tag . $matches[0]; 1968 $text = substr($text, strlen($matches[0])); 1969 } 1970 else { 1959 $text = substr( $text, strlen( $matches[0] ) ); 1960 } else { 1971 1961 # No end marker: just skip it. 1972 1962 $parsed .= $tag; … … 1975 1965 # 1976 1966 # Check for: Opening Block level tag or 1977 # Opening Context Block tag (like ins and del) 1967 # Opening Context Block tag (like ins and del) 1978 1968 # used as a block tag (tag is alone on it's line). 1979 1969 # 1980 else if (preg_match('{^<(?:'.$this->block_tags_re.')\b}', $tag) || 1981 ( preg_match('{^<(?:'.$this->context_block_tags_re.')\b}', $tag) && 1982 preg_match($newline_before_re, $parsed) && 1983 preg_match($newline_after_re, $text) ) 1984 ) 1985 { 1970 else if ( preg_match( '{^<(?:' . $this->block_tags_re . ')\b}', $tag ) || 1971 ( preg_match( '{^<(?:' . $this->context_block_tags_re . ')\b}', $tag ) && 1972 preg_match( $newline_before_re, $parsed ) && 1973 preg_match( $newline_after_re, $text ) ) 1974 ) { 1986 1975 # Need to parse tag and following text using the HTML parser. 1987 list( $block_text, $text) =1988 $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashBlock", true);1989 1976 list( $block_text, $text ) = 1977 $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashBlock", true ); 1978 1990 1979 # Make sure it stays outside of any paragraph by adding newlines. 1991 1980 $parsed .= "\n\n$block_text\n\n"; … … 1995 1984 # HTML Comments, processing instructions. 1996 1985 # 1997 else if ( preg_match('{^<(?:'.$this->clean_tags_re.')\b}', $tag) ||1998 $tag{1} == '!' || $tag{1} == '?')1999 {1986 else if ( preg_match( '{^<(?:' . $this->clean_tags_re . ')\b}', $tag ) || 1987 $tag{1} == '!' || $tag{1} == '?' 1988 ) { 2000 1989 # Need to parse tag and following text using the HTML parser. 2001 1990 # (don't check for markdown attribute) 2002 list( $block_text, $text) =2003 $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashClean", false);2004 1991 list( $block_text, $text ) = 1992 $this->_hashHTMLBlocks_inHTML( $tag . $text, "hashClean", false ); 1993 2005 1994 $parsed .= $block_text; 2006 1995 } … … 2008 1997 # Check for: Tag with same name as enclosing tag. 2009 1998 # 2010 else if ( $enclosing_tag_re !== '' &&2011 2012 preg_match('{^</?(?:'.$enclosing_tag_re.')\b}', $tag))2013 {1999 else if ( $enclosing_tag_re !== '' && 2000 # Same name as enclosing tag. 2001 preg_match( '{^</?(?:' . $enclosing_tag_re . ')\b}', $tag ) 2002 ) { 2014 2003 # 2015 2004 # Increase/decrease nested tag count. 2016 2005 # 2017 if ($tag{1} == '/') $depth--; 2018 else if ($tag{strlen($tag)-2} != '/') $depth++; 2019 2020 if ($depth < 0) { 2006 if ( $tag{1} == '/' ) { 2007 $depth --; 2008 } else if ( $tag{strlen( $tag ) - 2} != '/' ) { 2009 $depth ++; 2010 } 2011 2012 if ( $depth < 0 ) { 2021 2013 # 2022 2014 # Going out of parent element. Clean up and break so we … … 2026 2018 break; 2027 2019 } 2028 2020 2029 2021 $parsed .= $tag; 2030 } 2031 else { 2022 } else { 2032 2023 $parsed .= $tag; 2033 2024 } 2034 } while ($depth >= 0); 2035 2036 return array($parsed, $text); 2037 } 2038 function _hashHTMLBlocks_inHTML($text, $hash_method, $md_attr) { 2039 # 2040 # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. 2041 # 2042 # * Calls $hash_method to convert any blocks. 2043 # * Stops when the first opening tag closes. 2044 # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. 2045 # (it is not inside clean tags) 2046 # 2047 # Returns an array of that form: ( processed text , remaining text ) 2048 # 2049 if ($text === '') return array('', ''); 2050 2025 } while ( $depth >= 0 ); 2026 2027 return array( $parsed, $text ); 2028 } 2029 2030 function _hashHTMLBlocks_inHTML( $text, $hash_method, $md_attr ) { 2031 # 2032 # Parse HTML, calling _HashHTMLBlocks_InMarkdown for block tags. 2033 # 2034 # * Calls $hash_method to convert any blocks. 2035 # * Stops when the first opening tag closes. 2036 # * $md_attr indicate if the use of the `markdown="1"` attribute is allowed. 2037 # (it is not inside clean tags) 2038 # 2039 # Returns an array of that form: ( processed text , remaining text ) 2040 # 2041 if ( $text === '' ) { 2042 return array( '', '' ); 2043 } 2044 2051 2045 # Regex to match `markdown` attribute inside of a tag. 2052 2046 $markdown_attr_re = ' … … 2056 2050 \s*=\s* 2057 2051 (?> 2058 (["\']) # $1: quote delimiter 2052 (["\']) # $1: quote delimiter 2059 2053 (.*?) # $2: attribute value 2060 \1 # matching delimiter 2054 \1 # matching delimiter 2061 2055 | 2062 2056 ([^\s>]*) # $3: unquoted attribute value … … 2064 2058 () # $4: make $3 always defined (avoid warnings) 2065 2059 }xs'; 2066 2060 2067 2061 # Regex to match any tag. 2068 2062 $tag_re = '{ … … 2087 2081 ) 2088 2082 }xs'; 2089 2090 $original_text = $text; 2091 2092 $depth = 0;# Current depth inside the tag tree.2093 $block_text = "";# Temporary text holder for current text.2094 $parsed = "";# Parsed text that will be returned.2083 2084 $original_text = $text; # Save original text in case of faliure. 2085 2086 $depth = 0; # Current depth inside the tag tree. 2087 $block_text = ""; # Temporary text holder for current text. 2088 $parsed = ""; # Parsed text that will be returned. 2095 2089 2096 2090 # … … 2098 2092 # (This pattern makes $base_tag_name_re safe without quoting.) 2099 2093 # 2100 if ( preg_match('/^<([\w:$]*)\b/', $text, $matches))2094 if ( preg_match( '/^<([\w:$]*)\b/', $text, $matches ) ) { 2101 2095 $base_tag_name_re = $matches[1]; 2096 } 2102 2097 2103 2098 # … … 2108 2103 # Split the text using the first $tag_match pattern found. 2109 2104 # Text before pattern will be first in the array, text after 2110 # pattern will be at the end, and between will be any catches made 2105 # pattern will be at the end, and between will be any catches made 2111 2106 # by the pattern. 2112 2107 # 2113 $parts = preg_split( $tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);2114 2115 if ( count($parts) < 3) {2108 $parts = preg_split( $tag_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE ); 2109 2110 if ( count( $parts ) < 3 ) { 2116 2111 # 2117 2112 # End of $text reached with unbalenced tag(s). 2118 2113 # In that case, we return original text unchanged and pass the 2119 # first character as filtered to prevent an infinite loop in the 2114 # first character as filtered to prevent an infinite loop in the 2120 2115 # parent function. 2121 2116 # 2122 return array( $original_text{0}, substr($original_text, 1));2123 } 2124 2117 return array( $original_text{0}, substr( $original_text, 1 ) ); 2118 } 2119 2125 2120 $block_text .= $parts[0]; # Text before current tag. 2126 $tag 2127 $text 2128 2121 $tag = $parts[1]; # Tag to handle. 2122 $text = $parts[2]; # Remaining text after current tag. 2123 2129 2124 # 2130 2125 # Check for: Auto-close tag (like <hr/>) 2131 2126 # Comments and Processing Instructions. 2132 2127 # 2133 if ( preg_match('{^</?(?:'.$this->auto_close_tags_re.')\b}', $tag) ||2134 $tag{1} == '!' || $tag{1} == '?')2135 {2128 if ( preg_match( '{^</?(?:' . $this->auto_close_tags_re . ')\b}', $tag ) || 2129 $tag{1} == '!' || $tag{1} == '?' 2130 ) { 2136 2131 # Just add the tag to the block as if it was text. 2137 2132 $block_text .= $tag; 2138 } 2139 else { 2133 } else { 2140 2134 # 2141 2135 # Increase/decrease nested tag count. Only do so if 2142 2136 # the tag's name match base tag's. 2143 2137 # 2144 if (preg_match('{^</?'.$base_tag_name_re.'\b}', $tag)) { 2145 if ($tag{1} == '/') $depth--; 2146 else if ($tag{strlen($tag)-2} != '/') $depth++; 2138 if ( preg_match( '{^</?' . $base_tag_name_re . '\b}', $tag ) ) { 2139 if ( $tag{1} == '/' ) { 2140 $depth --; 2141 } else if ( $tag{strlen( $tag ) - 2} != '/' ) { 2142 $depth ++; 2143 } 2147 2144 } 2148 2145 2149 2146 # 2150 2147 # Check for `markdown="1"` attribute and handle it. 2151 2148 # 2152 if ( $md_attr &&2153 preg_match($markdown_attr_re, $tag, $attr_m) &&2154 preg_match('/^1|block|span$/', $attr_m[2] . $attr_m[3]))2155 {2149 if ( $md_attr && 2150 preg_match( $markdown_attr_re, $tag, $attr_m ) && 2151 preg_match( '/^1|block|span$/', $attr_m[2] . $attr_m[3] ) 2152 ) { 2156 2153 # Remove `markdown` attribute from opening tag. 2157 $tag = preg_replace( $markdown_attr_re, '', $tag);2158 2154 $tag = preg_replace( $markdown_attr_re, '', $tag ); 2155 2159 2156 # Check if text inside this tag must be parsed in span mode. 2160 2157 $this->mode = $attr_m[2] . $attr_m[3]; 2161 $span_mode = $this->mode == 'span' || $this->mode != 'block' &&2162 preg_match('{^<(?:'.$this->contain_span_tags_re.')\b}', $tag);2163 2158 $span_mode = $this->mode == 'span' || $this->mode != 'block' && 2159 preg_match( '{^<(?:' . $this->contain_span_tags_re . ')\b}', $tag ); 2160 2164 2161 # Calculate indent before tag. 2165 if ( preg_match('/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches)) {2162 if ( preg_match( '/(?:^|\n)( *?)(?! ).*?$/', $block_text, $matches ) ) { 2166 2163 $strlen = $this->utf8_strlen; 2167 $indent = $strlen( $matches[1], 'UTF-8');2164 $indent = $strlen( $matches[1], 'UTF-8' ); 2168 2165 } else { 2169 2166 $indent = 0; 2170 2167 } 2171 2168 2172 2169 # End preceding block with this tag. 2173 2170 $block_text .= $tag; 2174 $parsed .= $this->$hash_method( $block_text);2175 2171 $parsed .= $this->$hash_method( $block_text ); 2172 2176 2173 # Get enclosing tag name for the ParseMarkdown function. 2177 2174 # (This pattern makes $tag_name_re safe without quoting.) 2178 preg_match( '/^<([\w:$]*)\b/', $tag, $matches);2175 preg_match( '/^<([\w:$]*)\b/', $tag, $matches ); 2179 2176 $tag_name_re = $matches[1]; 2180 2177 2181 2178 # Parse the content using the HTML-in-Markdown parser. 2182 list ( $block_text, $text)2183 = $this->_hashHTMLBlocks_inMarkdown( $text, $indent,2184 $tag_name_re, $span_mode);2185 2179 list ( $block_text, $text ) 2180 = $this->_hashHTMLBlocks_inMarkdown( $text, $indent, 2181 $tag_name_re, $span_mode ); 2182 2186 2183 # Outdent markdown text. 2187 if ( $indent > 0) {2188 $block_text = preg_replace( "/^[ ]{1,$indent}/m", "",2189 $block_text);2184 if ( $indent > 0 ) { 2185 $block_text = preg_replace( "/^[ ]{1,$indent}/m", "", 2186 $block_text ); 2190 2187 } 2191 2188 2192 2189 # Append tag content to parsed text. 2193 if (!$span_mode) $parsed .= "\n\n$block_text\n\n"; 2194 else $parsed .= "$block_text"; 2195 2190 if ( ! $span_mode ) { 2191 $parsed .= "\n\n$block_text\n\n"; 2192 } else { 2193 $parsed .= "$block_text"; 2194 } 2195 2196 2196 # Start over a new block. 2197 2197 $block_text = ""; 2198 } else { 2199 $block_text .= $tag; 2198 2200 } 2199 else $block_text .= $tag; 2200 } 2201 2202 } while ($depth > 0); 2203 2201 } 2202 } while ( $depth > 0 ); 2203 2204 2204 # 2205 2205 # Hash last block text that wasn't processed inside the loop. 2206 2206 # 2207 $parsed .= $this->$hash_method($block_text); 2208 2209 return array($parsed, $text); 2210 } 2211 2212 2213 function hashClean($text) { 2214 # 2215 # Called whenever a tag must be hashed when a function insert a "clean" tag 2216 # in $text, it pass through this function and is automaticaly escaped, 2217 # blocking invalid nested overlap. 2218 # 2219 return $this->hashPart($text, 'C'); 2220 } 2221 2222 2223 function doHeaders($text) { 2224 # 2225 # Redefined to add id attribute support. 2226 # 2207 $parsed .= $this->$hash_method( $block_text ); 2208 2209 return array( $parsed, $text ); 2210 } 2211 2212 function hashClean( $text ) { 2213 # 2214 # Called whenever a tag must be hashed when a function insert a "clean" tag 2215 # in $text, it pass through this function and is automaticaly escaped, 2216 # blocking invalid nested overlap. 2217 # 2218 return $this->hashPart( $text, 'C' ); 2219 } 2220 2221 function doHeaders( $text ) { 2222 # 2223 # Redefined to add id attribute support. 2224 # 2227 2225 # Setext-style headers: 2228 2226 # Header 1 {#header1} 2229 2227 # ======== 2230 # 2228 # 2231 2229 # Header 2 {#header2} 2232 2230 # -------- … … 2238 2236 [ ]*\n(=+|-+)[ ]*\n+ # $3: Header footer 2239 2237 }mx', 2240 array( &$this, '_doHeaders_callback_setext'), $text);2238 array( &$this, '_doHeaders_callback_setext' ), $text ); 2241 2239 2242 2240 # atx-style headers: … … 2247 2245 # ###### Header 6 {#header2} 2248 2246 # 2249 $text = preg_replace_callback( '{2247 $text = preg_replace_callback( '{ 2250 2248 ^(\#{1,6}) # $1 = string of #\'s 2251 2249 [ ]* … … 2257 2255 \n+ 2258 2256 }xm', 2259 array( &$this, '_doHeaders_callback_atx'), $text);2257 array( &$this, '_doHeaders_callback_atx' ), $text ); 2260 2258 2261 2259 return $text; 2262 2260 } 2263 function _doHeaders_attr($attr) { 2264 if (empty($attr)) return ""; 2261 2262 function _doHeaders_attr( $attr ) { 2263 if ( empty( $attr ) ) { 2264 return ""; 2265 } 2266 2265 2267 return " id=\"$attr\""; 2266 2268 } 2267 function _doHeaders_callback_setext($matches) { 2268 if ($matches[3] == '-' && preg_match('{^- }', $matches[1])) 2269 2270 function _doHeaders_callback_setext( $matches ) { 2271 if ( $matches[3] == '-' && preg_match( '{^- }', $matches[1] ) ) { 2269 2272 return $matches[0]; 2273 } 2270 2274 $level = $matches[3]{0} == '=' ? 1 : 2; 2271 $attr = $this->_doHeaders_attr($id =& $matches[2]); 2272 $block = "<h$level$attr>".$this->runSpanGamut($matches[1])."</h$level>"; 2273 return "\n" . $this->hashBlock($block) . "\n\n"; 2274 } 2275 function _doHeaders_callback_atx($matches) { 2276 $level = strlen($matches[1]); 2277 $attr = $this->_doHeaders_attr($id =& $matches[3]); 2278 $block = "<h$level$attr>".$this->runSpanGamut($matches[2])."</h$level>"; 2279 return "\n" . $this->hashBlock($block) . "\n\n"; 2280 } 2281 2282 2283 function doTables($text) { 2284 # 2285 # Form HTML tables. 2286 # 2275 $attr = $this->_doHeaders_attr( $id =& $matches[2] ); 2276 $block = "<h$level$attr>" . $this->runSpanGamut( $matches[1] ) . "</h$level>"; 2277 2278 return "\n" . $this->hashBlock( $block ) . "\n\n"; 2279 } 2280 2281 function _doHeaders_callback_atx( $matches ) { 2282 $level = strlen( $matches[1] ); 2283 $attr = $this->_doHeaders_attr( $id =& $matches[3] ); 2284 $block = "<h$level$attr>" . $this->runSpanGamut( $matches[2] ) . "</h$level>"; 2285 2286 return "\n" . $this->hashBlock( $block ) . "\n\n"; 2287 } 2288 2289 function doTables( $text ) { 2290 # 2291 # Form HTML tables. 2292 # 2287 2293 $less_than_tab = $this->tab_width - 1; 2288 2294 # … … 2294 2300 # | Cell 3 | Cell 4 2295 2301 # 2296 $text = preg_replace_callback( '2302 $text = preg_replace_callback( ' 2297 2303 { 2298 2304 ^ # Start of a line 2299 [ ]{0,' .$less_than_tab.'} # Allowed whitespace.2305 [ ]{0,' . $less_than_tab . '} # Allowed whitespace. 2300 2306 [|] # Optional leading pipe (present) 2301 2307 (.+) \n # $1: Header row (at least one pipe) 2302 2303 [ ]{0,' .$less_than_tab.'} # Allowed whitespace.2308 2309 [ ]{0,' . $less_than_tab . '} # Allowed whitespace. 2304 2310 [|] ([ ]*[-:]+[-| :]*) \n # $2: Header underline 2305 2311 2306 2312 ( # $3: Cells 2307 2313 (?> … … 2312 2318 (?=\n|\Z) # Stop at final double newline. 2313 2319 }xm', 2314 array( &$this, '_doTable_leadingPipe_callback'), $text);2315 2320 array( &$this, '_doTable_leadingPipe_callback' ), $text ); 2321 2316 2322 # 2317 2323 # Find tables without leading pipe. … … 2322 2328 # Cell 3 | Cell 4 2323 2329 # 2324 $text = preg_replace_callback( '2330 $text = preg_replace_callback( ' 2325 2331 { 2326 2332 ^ # Start of a line 2327 [ ]{0,' .$less_than_tab.'} # Allowed whitespace.2333 [ ]{0,' . $less_than_tab . '} # Allowed whitespace. 2328 2334 (\S.*[|].*) \n # $1: Header row (at least one pipe) 2329 2330 [ ]{0,' .$less_than_tab.'} # Allowed whitespace.2335 2336 [ ]{0,' . $less_than_tab . '} # Allowed whitespace. 2331 2337 ([-:]+[ ]*[|][-| :]*) \n # $2: Header underline 2332 2338 2333 2339 ( # $3: Cells 2334 2340 (?> … … 2338 2344 (?=\n|\Z) # Stop at final double newline. 2339 2345 }xm', 2340 array( &$this, '_DoTable_callback'), $text);2346 array( &$this, '_DoTable_callback' ), $text ); 2341 2347 2342 2348 return $text; 2343 2349 } 2344 function _doTable_leadingPipe_callback($matches) { 2345 $head = $matches[1]; 2346 $underline = $matches[2]; 2347 $content = $matches[3]; 2348 2350 2351 function _doTable_leadingPipe_callback( $matches ) { 2352 $head = $matches[1]; 2353 $underline = $matches[2]; 2354 $content = $matches[3]; 2355 2349 2356 # Remove leading pipe for each row. 2350 $content = preg_replace('/^ *[|]/m', '', $content); 2351 2352 return $this->_doTable_callback(array($matches[0], $head, $underline, $content)); 2353 } 2354 function _doTable_callback($matches) { 2355 $head = $matches[1]; 2356 $underline = $matches[2]; 2357 $content = $matches[3]; 2357 $content = preg_replace( '/^ *[|]/m', '', $content ); 2358 2359 return $this->_doTable_callback( array( $matches[0], $head, $underline, $content ) ); 2360 } 2361 2362 function _doTable_callback( $matches ) { 2363 $head = $matches[1]; 2364 $underline = $matches[2]; 2365 $content = $matches[3]; 2358 2366 2359 2367 # Remove any tailing pipes for each line. 2360 $head = preg_replace('/[|] *$/m', '', $head);2361 $underline = preg_replace('/[|] *$/m', '', $underline);2362 $content = preg_replace('/[|] *$/m', '', $content);2363 2368 $head = preg_replace( '/[|] *$/m', '', $head ); 2369 $underline = preg_replace( '/[|] *$/m', '', $underline ); 2370 $content = preg_replace( '/[|] *$/m', '', $content ); 2371 2364 2372 # Reading alignement from header underline. 2365 $separators = preg_split('/ *[|] */', $underline); 2366 foreach ($separators as $n => $s) { 2367 if (preg_match('/^ *-+: *$/', $s)) $attr[$n] = ' align="right"'; 2368 else if (preg_match('/^ *:-+: *$/', $s))$attr[$n] = ' align="center"'; 2369 else if (preg_match('/^ *:-+ *$/', $s)) $attr[$n] = ' align="left"'; 2370 else $attr[$n] = ''; 2371 } 2372 2373 # Parsing span elements, including code spans, character escapes, 2373 $separators = preg_split( '/ *[|] */', $underline ); 2374 foreach ( $separators as $n => $s ) { 2375 if ( preg_match( '/^ *-+: *$/', $s ) ) { 2376 $attr[ $n ] = ' align="right"'; 2377 } else if ( preg_match( '/^ *:-+: *$/', $s ) ) { 2378 $attr[ $n ] = ' align="center"'; 2379 } else if ( preg_match( '/^ *:-+ *$/', $s ) ) { 2380 $attr[ $n ] = ' align="left"'; 2381 } else { 2382 $attr[ $n ] = ''; 2383 } 2384 } 2385 2386 # Parsing span elements, including code spans, character escapes, 2374 2387 # and inline HTML tags, so that pipes inside those gets ignored. 2375 $head = $this->parseSpan($head);2376 $headers = preg_split('/ *[|] */', $head);2377 $col_count = count($headers);2378 2388 $head = $this->parseSpan( $head ); 2389 $headers = preg_split( '/ *[|] */', $head ); 2390 $col_count = count( $headers ); 2391 2379 2392 # Write column headers. 2380 2393 $text = "<table>\n"; 2381 2394 $text .= "<thead>\n"; 2382 2395 $text .= "<tr>\n"; 2383 foreach ($headers as $n => $header) 2384 $text .= " <th$attr[$n]>".$this->runSpanGamut(trim($header))."</th>\n"; 2396 foreach ( $headers as $n => $header ) { 2397 $text .= " <th$attr[$n]>" . $this->runSpanGamut( trim( $header ) ) . "</th>\n"; 2398 } 2385 2399 $text .= "</tr>\n"; 2386 2400 $text .= "</thead>\n"; 2387 2401 2388 2402 # Split content by row. 2389 $rows = explode( "\n", trim($content, "\n"));2390 2403 $rows = explode( "\n", trim( $content, "\n" ) ); 2404 2391 2405 $text .= "<tbody>\n"; 2392 foreach ( $rows as $row) {2393 # Parsing span elements, including code spans, character escapes, 2406 foreach ( $rows as $row ) { 2407 # Parsing span elements, including code spans, character escapes, 2394 2408 # and inline HTML tags, so that pipes inside those gets ignored. 2395 $row = $this->parseSpan( $row);2396 2409 $row = $this->parseSpan( $row ); 2410 2397 2411 # Split row by cell. 2398 $row_cells = preg_split( '/ *[|] */', $row, $col_count);2399 $row_cells = array_pad( $row_cells, $col_count, '');2400 2412 $row_cells = preg_split( '/ *[|] */', $row, $col_count ); 2413 $row_cells = array_pad( $row_cells, $col_count, '' ); 2414 2401 2415 $text .= "<tr>\n"; 2402 foreach ($row_cells as $n => $cell) 2403 $text .= " <td$attr[$n]>".$this->runSpanGamut(trim($cell))."</td>\n"; 2416 foreach ( $row_cells as $n => $cell ) { 2417 $text .= " <td$attr[$n]>" . $this->runSpanGamut( trim( $cell ) ) . "</td>\n"; 2418 } 2404 2419 $text .= "</tr>\n"; 2405 2420 } 2406 2421 $text .= "</tbody>\n"; 2407 2422 $text .= "</table>"; 2408 2409 return $this->hashBlock($text) . "\n"; 2410 } 2411 2412 2413 function doDefLists($text) { 2414 # 2415 # Form HTML definition lists. 2416 # 2423 2424 return $this->hashBlock( $text ) . "\n"; 2425 } 2426 2427 function doDefLists( $text ) { 2428 # 2429 # Form HTML definition lists. 2430 # 2417 2431 $less_than_tab = $this->tab_width - 1; 2418 2432 … … 2421 2435 ( # $1 = whole list 2422 2436 ( # $2 2423 [ ]{0,' .$less_than_tab.'}2437 [ ]{0,' . $less_than_tab . '} 2424 2438 ((?>.*\S.*\n)+) # $3 = defined term 2425 2439 \n? 2426 [ ]{0,' .$less_than_tab.'}:[ ]+ # colon starting definition2440 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition 2427 2441 ) 2428 2442 (?s:.+?) … … 2433 2447 (?=\S) 2434 2448 (?! # Negative lookahead for another term 2435 [ ]{0,' .$less_than_tab.'}2449 [ ]{0,' . $less_than_tab . '} 2436 2450 (?: \S.*\n )+? # defined term 2437 2451 \n? 2438 [ ]{0,' .$less_than_tab.'}:[ ]+ # colon starting definition2452 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition 2439 2453 ) 2440 2454 (?! # Negative lookahead for another definition 2441 [ ]{0,' .$less_than_tab.'}:[ ]+ # colon starting definition2455 [ ]{0,' . $less_than_tab . '}:[ ]+ # colon starting definition 2442 2456 ) 2443 2457 ) … … 2445 2459 )'; // mx 2446 2460 2447 $text = preg_replace_callback( '{2461 $text = preg_replace_callback( '{ 2448 2462 (?>\A\n?|(?<=\n\n)) 2449 ' .$whole_list_re.'2463 ' . $whole_list_re . ' 2450 2464 }mx', 2451 array( &$this, '_doDefLists_callback'), $text);2465 array( &$this, '_doDefLists_callback' ), $text ); 2452 2466 2453 2467 return $text; 2454 2468 } 2455 function _doDefLists_callback($matches) { 2469 2470 function _doDefLists_callback( $matches ) { 2456 2471 # Re-usable patterns to match list item bullets and number markers: 2457 2472 $list = $matches[1]; 2458 2473 2459 2474 # Turn double returns into triple returns, so that we can make a 2460 2475 # paragraph for the last item in a list, if necessary: 2461 $result = trim( $this->processDefListItems($list));2476 $result = trim( $this->processDefListItems( $list ) ); 2462 2477 $result = "<dl>\n" . $result . "\n</dl>"; 2463 return $this->hashBlock($result) . "\n\n"; 2464 }2465 2466 2467 function processDefListItems( $list_str) {2468 #2469 # Process the contents of a single definition list, splitting it2470 # into individual term and definition list items.2471 #2478 2479 return $this->hashBlock( $result ) . "\n\n"; 2480 } 2481 2482 function processDefListItems( $list_str ) { 2483 # 2484 # Process the contents of a single definition list, splitting it 2485 # into individual term and definition list items. 2486 # 2472 2487 $less_than_tab = $this->tab_width - 1; 2473 2488 2474 2489 # trim trailing blank lines: 2475 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str);2490 $list_str = preg_replace( "/\n{2,}\\z/", "\n", $list_str ); 2476 2491 2477 2492 # Process definition terms. 2478 $list_str = preg_replace_callback( '{2493 $list_str = preg_replace_callback( '{ 2479 2494 (?>\A\n?|\n\n+) # leading line 2480 2495 ( # definition terms = $1 2481 [ ]{0,' .$less_than_tab.'} # leading whitespace2482 (?![:][ ]|[ ]) # negative lookahead for a definition 2496 [ ]{0,' . $less_than_tab . '} # leading whitespace 2497 (?![:][ ]|[ ]) # negative lookahead for a definition 2483 2498 # mark (colon) or more whitespace. 2484 (?> \S.* \n)+? # actual term (not whitespace). 2485 ) 2486 (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed 2499 (?> \S.* \n)+? # actual term (not whitespace). 2500 ) 2501 (?=\n?[ ]{0,3}:[ ]) # lookahead for following line feed 2487 2502 # with a definition mark. 2488 2503 }xm', 2489 array( &$this, '_processDefListItems_callback_dt'), $list_str);2504 array( &$this, '_processDefListItems_callback_dt' ), $list_str ); 2490 2505 2491 2506 # Process actual definitions. 2492 $list_str = preg_replace_callback( '{2507 $list_str = preg_replace_callback( '{ 2493 2508 \n(\n+)? # leading line = $1 2494 2509 ( # marker space = $2 2495 [ ]{0,' .$less_than_tab.'} # whitespace before colon2510 [ ]{0,' . $less_than_tab . '} # whitespace before colon 2496 2511 [:][ ]+ # definition mark (colon) 2497 2512 ) … … 2499 2514 (?= \n+ # stop at next definition mark, 2500 2515 (?: # next term or end of text 2501 [ ]{0,' .$less_than_tab.'} [:][ ] |2516 [ ]{0,' . $less_than_tab . '} [:][ ] | 2502 2517 <dt> | \z 2503 ) 2504 ) 2518 ) 2519 ) 2505 2520 }xm', 2506 array( &$this, '_processDefListItems_callback_dd'), $list_str);2521 array( &$this, '_processDefListItems_callback_dd' ), $list_str ); 2507 2522 2508 2523 return $list_str; 2509 2524 } 2510 function _processDefListItems_callback_dt($matches) { 2511 $terms = explode("\n", trim($matches[1])); 2512 $text = ''; 2513 foreach ($terms as $term) { 2514 $term = $this->runSpanGamut(trim($term)); 2525 2526 function _processDefListItems_callback_dt( $matches ) { 2527 $terms = explode( "\n", trim( $matches[1] ) ); 2528 $text = ''; 2529 foreach ( $terms as $term ) { 2530 $term = $this->runSpanGamut( trim( $term ) ); 2515 2531 $text .= "\n<dt>" . $term . "</dt>"; 2516 2532 } 2533 2517 2534 return $text . "\n"; 2518 2535 } 2519 function _processDefListItems_callback_dd($matches) { 2520 $leading_line = $matches[1]; 2521 $marker_space = $matches[2]; 2522 $def = $matches[3]; 2523 2524 if ($leading_line || preg_match('/\n{2,}/', $def)) { 2536 2537 function _processDefListItems_callback_dd( $matches ) { 2538 $leading_line = $matches[1]; 2539 $marker_space = $matches[2]; 2540 $def = $matches[3]; 2541 2542 if ( $leading_line || preg_match( '/\n{2,}/', $def ) ) { 2525 2543 # Replace marker with the appropriate whitespace indentation 2526 $def = str_repeat(' ', strlen($marker_space)) . $def; 2527 $def = $this->runBlockGamut($this->outdent($def . "\n\n")); 2528 $def = "\n". $def ."\n"; 2529 } 2530 else { 2531 $def = rtrim($def); 2532 $def = $this->runSpanGamut($this->outdent($def)); 2544 $def = str_repeat( ' ', strlen( $marker_space ) ) . $def; 2545 $def = $this->runBlockGamut( $this->outdent( $def . "\n\n" ) ); 2546 $def = "\n" . $def . "\n"; 2547 } else { 2548 $def = rtrim( $def ); 2549 $def = $this->runSpanGamut( $this->outdent( $def ) ); 2533 2550 } 2534 2551 … … 2536 2553 } 2537 2554 2538 2539 function doFencedCodeBlocks($text) { 2540 # 2541 # Adding the fenced code block syntax to regular Markdown: 2542 # 2543 # ~~~ 2544 # Code block 2545 # ~~~ 2546 # 2555 function doFencedCodeBlocks( $text ) { 2556 # 2557 # Adding the fenced code block syntax to regular Markdown: 2558 # 2559 # ~~~ 2560 # Code block 2561 # ~~~ 2562 # 2547 2563 $less_than_tab = $this->tab_width; 2548 &nbs