Changeset 2501
- Timestamp:
- 02/15/2016 06:28:02 AM (10 years ago)
- Location:
- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory
- Files:
-
- 3 added
- 4 edited
-
class-wporg-plugin-directory-template.php (added)
-
class-wporg-plugin-directory-tools.php (added)
-
class-wporg-plugin-directory.php (added)
-
plugin-directory.php (modified) (1 diff)
-
readme-parser/ReadmeParser.php (modified) (10 diffs)
-
readme-parser/compat.php (modified) (1 diff)
-
readme-parser/markdown.php (modified) (84 diffs)
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', 4 );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', 'MarkdownExtra_Parser' );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 'script|noscript|form|fieldset|iframe|math';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 .= " title=\"$title\"";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 .= " title=\"$title\"";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 .= " title=\"$title\"";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" => 5,1717 "doAbbreviations" => 70,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 # Same name as enclosing tag.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; # Save original text in case of faliure.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 = $parts[1]; # Tag to handle.2127 $text = $parts[2]; # Remaining text after current tag.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 2549 $text = preg_replace_callback( '{2564 2565 $text = preg_replace_callback( '{ 2550 2566 (?:\n|\A) 2551 2567 # 1: Opening marker … … 2554 2570 ) 2555 2571 [ ]* \n # Whitespace and newline following marker. 2556 2572 2557 2573 # 2: Content 2558 2574 ( … … 2562 2578 )+ 2563 2579 ) 2564 2580 2565 2581 # Closing marker. 2566 2582 \1 [ ]* \n 2567 2583 }xm', 2568 array( &$this, '_doFencedCodeBlocks_callback'), $text);2584 array( &$this, '_doFencedCodeBlocks_callback' ), $text ); 2569 2585 2570 2586 return $text; 2571 2587 } 2572 function _doFencedCodeBlocks_callback($matches) { 2588 2589 function _doFencedCodeBlocks_callback( $matches ) { 2573 2590 $codeblock = $matches[2]; 2574 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES);2575 $codeblock = preg_replace_callback( '/^\n+/',2576 array( &$this, '_doFencedCodeBlocks_newlines'), $codeblock);2591 $codeblock = htmlspecialchars( $codeblock, ENT_NOQUOTES ); 2592 $codeblock = preg_replace_callback( '/^\n+/', 2593 array( &$this, '_doFencedCodeBlocks_newlines' ), $codeblock ); 2577 2594 $codeblock = "<pre><code>$codeblock</code></pre>"; 2578 return "\n\n".$this->hashBlock($codeblock)."\n\n"; 2579 } 2580 function _doFencedCodeBlocks_newlines($matches) { 2581 return str_repeat("<br$this->empty_element_suffix", 2582 strlen($matches[0])); 2595 2596 return "\n\n" . $this->hashBlock( $codeblock ) . "\n\n"; 2597 } 2598 2599 function _doFencedCodeBlocks_newlines( $matches ) { 2600 return str_repeat( "<br$this->empty_element_suffix", 2601 strlen( $matches[0] ) ); 2583 2602 } 2584 2603 … … 2592 2611 '*' => '(?<=\S|^)(?<!\*)\*(?!\*)', 2593 2612 '_' => '(?<=\S|^)(?<!_)_(?![a-zA-Z0-9_])', 2594 );2613 ); 2595 2614 var $strong_relist = array( 2596 2615 '' => '(?:(?<!\*)\*\*(?!\*)|(?<![a-zA-Z0-9_])__(?!_))(?=\S|$)(?![.,:;]\s)', 2597 2616 '**' => '(?<=\S|^)(?<!\*)\*\*(?!\*)', 2598 2617 '__' => '(?<=\S|^)(?<!_)__(?![a-zA-Z0-9_])', 2599 );2618 ); 2600 2619 var $em_strong_relist = array( 2601 2620 '' => '(?:(?<!\*)\*\*\*(?!\*)|(?<![a-zA-Z0-9_])___(?!_))(?=\S|$)(?![.,:;]\s)', 2602 2621 '***' => '(?<=\S|^)(?<!\*)\*\*\*(?!\*)', 2603 2622 '___' => '(?<=\S|^)(?<!_)___(?![a-zA-Z0-9_])', 2604 ); 2605 2606 2607 function formParagraphs($text) { 2608 # 2609 # Params: 2610 # $text - string to process with html <p> tags 2611 # 2623 ); 2624 2625 function formParagraphs( $text ) { 2626 # 2627 # Params: 2628 # $text - string to process with html <p> tags 2629 # 2612 2630 # Strip leading and trailing lines: 2613 $text = preg_replace( '/\A\n+|\n+\z/', '', $text);2614 2615 $grafs = preg_split( '/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);2631 $text = preg_replace( '/\A\n+|\n+\z/', '', $text ); 2632 2633 $grafs = preg_split( '/\n{2,}/', $text, - 1, PREG_SPLIT_NO_EMPTY ); 2616 2634 2617 2635 # 2618 2636 # Wrap <p> tags and unhashify HTML blocks 2619 2637 # 2620 foreach ( $grafs as $key => $value) {2621 $value = trim( $this->runSpanGamut($value));2622 2638 foreach ( $grafs as $key => $value ) { 2639 $value = trim( $this->runSpanGamut( $value ) ); 2640 2623 2641 # Check if this should be enclosed in a paragraph. 2624 2642 # Clean tag hashes & block tag hashes are left alone. 2625 $is_p = ! preg_match('/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value);2626 2627 if ( $is_p) {2643 $is_p = ! preg_match( '/^B\x1A[0-9]+B|^C\x1A[0-9]+C$/', $value ); 2644 2645 if ( $is_p ) { 2628 2646 $value = "<p>$value</p>"; 2629 2647 } 2630 $grafs[ $key] = $value;2631 } 2632 2633 # Join grafs in one text, then unhash HTML tags. 2634 $text = implode( "\n\n", $grafs);2635 2648 $grafs[ $key ] = $value; 2649 } 2650 2651 # Join grafs in one text, then unhash HTML tags. 2652 $text = implode( "\n\n", $grafs ); 2653 2636 2654 # Finish by removing any tag hashes still present in $text. 2637 $text = $this->unhash( $text);2638 2655 $text = $this->unhash( $text ); 2656 2639 2657 return $text; 2640 2658 } 2641 2642 2659 2643 2660 ### Footnotes 2644 2645 function stripFootnotes( $text) {2646 #2647 # Strips link definitions from text, stores the URLs and titles in2648 # hash references.2649 #2661 2662 function stripFootnotes( $text ) { 2663 # 2664 # Strips link definitions from text, stores the URLs and titles in 2665 # hash references. 2666 # 2650 2667 $less_than_tab = $this->tab_width - 1; 2651 2668 2652 2669 # Link defs are in the form: [^id]: url "optional title" 2653 $text = preg_replace_callback( '{2654 ^[ ]{0,' .$less_than_tab.'}\[\^(.+?)\][ ]?: # note_id = $12670 $text = preg_replace_callback( '{ 2671 ^[ ]{0,' . $less_than_tab . '}\[\^(.+?)\][ ]?: # note_id = $1 2655 2672 [ ]* 2656 2673 \n? # maybe *one* newline 2657 2674 ( # text = $2 (no blank lines allowed) 2658 (?: 2675 (?: 2659 2676 .+ # actual text 2660 2677 | 2661 \n # newlines but 2678 \n # newlines but 2662 2679 (?!\[\^.+?\]:\s)# negative lookahead for footnote marker. 2663 (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 2680 (?!\n+[ ]{0,3}\S)# ensure line is not blank and followed 2664 2681 # by non-indented content 2665 2682 )* 2666 ) 2683 ) 2667 2684 }xm', 2668 array(&$this, '_stripFootnotes_callback'), 2669 $text); 2685 array( &$this, '_stripFootnotes_callback' ), 2686 $text ); 2687 2670 2688 return $text; 2671 2689 } 2672 function _stripFootnotes_callback($matches) { 2673 $note_id = $this->fn_id_prefix . $matches[1]; 2674 $this->footnotes[$note_id] = $this->outdent($matches[2]); 2690 2691 function _stripFootnotes_callback( $matches ) { 2692 $note_id = $this->fn_id_prefix . $matches[1]; 2693 $this->footnotes[ $note_id ] = $this->outdent( $matches[2] ); 2694 2675 2695 return ''; # String that will replace the block 2676 2696 } 2677 2697 2678 2679 function doFootnotes($text) {2680 #2681 # Replace footnote references in $text [^id] with a special text-token2682 # which will be replaced by the actual footnote marker in appendFootnotes.2683 #2684 if (!$this->in_anchor) {2685 $text = preg_replace('{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text);2686 } 2698 function doFootnotes( $text ) { 2699 # 2700 # Replace footnote references in $text [^id] with a special text-token 2701 # which will be replaced by the actual footnote marker in appendFootnotes. 2702 # 2703 if ( ! $this->in_anchor ) { 2704 $text = preg_replace( '{\[\^(.+?)\]}', "F\x1Afn:\\1\x1A:", $text ); 2705 } 2706 2687 2707 return $text; 2688 2708 } 2689 2709 2690 2691 function appendFootnotes($text) { 2692 # 2693 # Append footnote list to text. 2694 # 2695 $text = preg_replace_callback('{F\x1Afn:(.*?)\x1A:}', 2696 array(&$this, '_appendFootnotes_callback'), $text); 2697 2698 if (!empty($this->footnotes_ordered)) { 2710 function appendFootnotes( $text ) { 2711 # 2712 # Append footnote list to text. 2713 # 2714 $text = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', 2715 array( &$this, '_appendFootnotes_callback' ), $text ); 2716 2717 if ( ! empty( $this->footnotes_ordered ) ) { 2699 2718 $text .= "\n\n"; 2700 2719 $text .= "<div class=\"footnotes\">\n"; 2701 $text .= "<hr" . $this->empty_element_suffix ."\n";2720 $text .= "<hr" . $this->empty_element_suffix . "\n"; 2702 2721 $text .= "<ol>\n\n"; 2703 2722 2704 2723 $attr = " rev=\"footnote\""; 2705 if ( $this->fn_backlink_class != "") {2724 if ( $this->fn_backlink_class != "" ) { 2706 2725 $class = $this->fn_backlink_class; 2707 $class = $this->encodeAttribute( $class);2726 $class = $this->encodeAttribute( $class ); 2708 2727 $attr .= " class=\"$class\""; 2709 2728 } 2710 if ( $this->fn_backlink_title != "") {2729 if ( $this->fn_backlink_title != "" ) { 2711 2730 $title = $this->fn_backlink_title; 2712 $title = $this->encodeAttribute( $title);2731 $title = $this->encodeAttribute( $title ); 2713 2732 $attr .= " title=\"$title\""; 2714 2733 } 2715 2734 $num = 0; 2716 2717 while ( !empty($this->footnotes_ordered)) {2718 $footnote = reset( $this->footnotes_ordered);2719 $note_id = key($this->footnotes_ordered);2720 unset( $this->footnotes_ordered[$note_id]);2721 2735 2736 while ( ! empty( $this->footnotes_ordered ) ) { 2737 $footnote = reset( $this->footnotes_ordered ); 2738 $note_id = key( $this->footnotes_ordered ); 2739 unset( $this->footnotes_ordered[ $note_id ] ); 2740 2722 2741 $footnote .= "\n"; # Need to append newline before parsing. 2723 $footnote = $this->runBlockGamut( "$footnote\n");2724 $footnote = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}',2725 array( &$this, '_appendFootnotes_callback'), $footnote);2726 2727 $attr = str_replace("%%", ++$num, $attr);2728 $note_id = $this->encodeAttribute( $note_id);2729 2742 $footnote = $this->runBlockGamut( "$footnote\n" ); 2743 $footnote = preg_replace_callback( '{F\x1Afn:(.*?)\x1A:}', 2744 array( &$this, '_appendFootnotes_callback' ), $footnote ); 2745 2746 $attr = str_replace( "%%", ++ $num, $attr ); 2747 $note_id = $this->encodeAttribute( $note_id ); 2748 2730 2749 # Add backlink to last paragraph; create new paragraph if needed. 2731 2750 $backlink = "<a href=\"#fnref:$note_id\"$attr>↩</a>"; 2732 if ( preg_match('{</p>$}', $footnote)) {2733 $footnote = substr( $footnote, 0, -4) . " $backlink</p>";2751 if ( preg_match( '{</p>$}', $footnote ) ) { 2752 $footnote = substr( $footnote, 0, - 4 ) . " $backlink</p>"; 2734 2753 } else { 2735 2754 $footnote .= "\n\n<p>$backlink</p>"; 2736 2755 } 2737 2756 2738 2757 $text .= "<li id=\"fn:$note_id\">\n"; 2739 2758 $text .= $footnote . "\n"; 2740 2759 $text .= "</li>\n\n"; 2741 2760 } 2742 2761 2743 2762 $text .= "</ol>\n"; 2744 2763 $text .= "</div>"; 2745 2764 } 2765 2746 2766 return $text; 2747 2767 } 2748 function _appendFootnotes_callback($matches) { 2768 2769 function _appendFootnotes_callback( $matches ) { 2749 2770 $node_id = $this->fn_id_prefix . $matches[1]; 2750 2771 2751 2772 # Create footnote marker only if it has a corresponding footnote *and* 2752 2773 # the footnote hasn't been used by another marker. 2753 if ( isset($this->footnotes[$node_id])) {2774 if ( isset( $this->footnotes[ $node_id ] ) ) { 2754 2775 # Transfert footnote content to the ordered list. 2755 $this->footnotes_ordered[ $node_id] = $this->footnotes[$node_id];2756 unset( $this->footnotes[$node_id]);2757 2758 $num = $this->footnote_counter++;2776 $this->footnotes_ordered[ $node_id ] = $this->footnotes[ $node_id ]; 2777 unset( $this->footnotes[ $node_id ] ); 2778 2779 $num = $this->footnote_counter ++; 2759 2780 $attr = " rel=\"footnote\""; 2760 if ( $this->fn_link_class != "") {2781 if ( $this->fn_link_class != "" ) { 2761 2782 $class = $this->fn_link_class; 2762 $class = $this->encodeAttribute( $class);2783 $class = $this->encodeAttribute( $class ); 2763 2784 $attr .= " class=\"$class\""; 2764 2785 } 2765 if ( $this->fn_link_title != "") {2786 if ( $this->fn_link_title != "" ) { 2766 2787 $title = $this->fn_link_title; 2767 $title = $this->encodeAttribute( $title);2788 $title = $this->encodeAttribute( $title ); 2768 2789 $attr .= " title=\"$title\""; 2769 2790 } 2770 2771 $attr = str_replace("%%", $num, $attr);2772 $node_id = $this->encodeAttribute( $node_id);2773 2791 2792 $attr = str_replace( "%%", $num, $attr ); 2793 $node_id = $this->encodeAttribute( $node_id ); 2794 2774 2795 return 2775 "<sup id=\"fnref:$node_id\">" .2776 "<a href=\"#fn:$node_id\"$attr>$num</a>" .2796 "<sup id=\"fnref:$node_id\">" . 2797 "<a href=\"#fn:$node_id\"$attr>$num</a>" . 2777 2798 "</sup>"; 2778 2799 } 2779 2780 return "[^".$matches[1]."]"; 2781 } 2782 2783 2800 2801 return "[^" . $matches[1] . "]"; 2802 } 2803 2784 2804 ### Abbreviations ### 2785 2786 function stripAbbreviations( $text) {2787 #2788 # Strips abbreviations from text, stores titles in hash references.2789 #2805 2806 function stripAbbreviations( $text ) { 2807 # 2808 # Strips abbreviations from text, stores titles in hash references. 2809 # 2790 2810 $less_than_tab = $this->tab_width - 1; 2791 2811 2792 2812 # Link defs are in the form: [id]*: url "optional title" 2793 $text = preg_replace_callback( '{2794 ^[ ]{0,' .$less_than_tab.'}\*\[(.+?)\][ ]?: # abbr_id = $12795 (.*) # text = $2 (no blank lines allowed) 2813 $text = preg_replace_callback( '{ 2814 ^[ ]{0,' . $less_than_tab . '}\*\[(.+?)\][ ]?: # abbr_id = $1 2815 (.*) # text = $2 (no blank lines allowed) 2796 2816 }xm', 2797 array(&$this, '_stripAbbreviations_callback'), 2798 $text); 2817 array( &$this, '_stripAbbreviations_callback' ), 2818 $text ); 2819 2799 2820 return $text; 2800 2821 } 2801 function _stripAbbreviations_callback($matches) { 2822 2823 function _stripAbbreviations_callback( $matches ) { 2802 2824 $abbr_word = $matches[1]; 2803 2825 $abbr_desc = $matches[2]; 2804 if ( $this->abbr_word_re)2826 if ( $this->abbr_word_re ) { 2805 2827 $this->abbr_word_re .= '|'; 2806 $this->abbr_word_re .= preg_quote($abbr_word); 2807 $this->abbr_desciptions[$abbr_word] = trim($abbr_desc); 2828 } 2829 $this->abbr_word_re .= preg_quote( $abbr_word ); 2830 $this->abbr_desciptions[ $abbr_word ] = trim( $abbr_desc ); 2831 2808 2832 return ''; # String that will replace the block 2809 2833 } 2810 2811 2812 function doAbbreviations($text) { 2813 # 2814 # Find defined abbreviations in text and wrap them in <abbr> elements. 2815 # 2816 if ($this->abbr_word_re) { 2817 // cannot use the /x modifier because abbr_word_re may 2834 2835 function doAbbreviations( $text ) { 2836 # 2837 # Find defined abbreviations in text and wrap them in <abbr> elements. 2838 # 2839 if ( $this->abbr_word_re ) { 2840 // cannot use the /x modifier because abbr_word_re may 2818 2841 // contain significant spaces: 2819 $text = preg_replace_callback('{'. 2820 '(?<![\w\x1A])'. 2821 '(?:'.$this->abbr_word_re.')'. 2822 '(?![\w\x1A])'. 2823 '}', 2824 array(&$this, '_doAbbreviations_callback'), $text); 2825 } 2842 $text = preg_replace_callback( '{' . 2843 '(?<![\w\x1A])' . 2844 '(?:' . $this->abbr_word_re . ')' . 2845 '(?![\w\x1A])' . 2846 '}', 2847 array( &$this, '_doAbbreviations_callback' ), $text ); 2848 } 2849 2826 2850 return $text; 2827 2851 } 2828 function _doAbbreviations_callback($matches) { 2852 2853 function _doAbbreviations_callback( $matches ) { 2829 2854 $abbr = $matches[0]; 2830 if ( isset($this->abbr_desciptions[$abbr])) {2831 $desc = $this->abbr_desciptions[ $abbr];2832 if ( empty($desc)) {2833 return $this->hashPart( "<abbr>$abbr</abbr>");2855 if ( isset( $this->abbr_desciptions[ $abbr ] ) ) { 2856 $desc = $this->abbr_desciptions[ $abbr ]; 2857 if ( empty( $desc ) ) { 2858 return $this->hashPart( "<abbr>$abbr</abbr>" ); 2834 2859 } else { 2835 $desc = $this->encodeAttribute($desc); 2836 return $this->hashPart("<abbr title=\"$desc\">$abbr</abbr>"); 2860 $desc = $this->encodeAttribute( $desc ); 2861 2862 return $this->hashPart( "<abbr title=\"$desc\">$abbr</abbr>" ); 2837 2863 } 2838 2864 } else { … … 2840 2866 } 2841 2867 } 2842 2843 2868 } 2844 2845 2869 2846 2870 /* … … 2852 2876 ----------- 2853 2877 2854 This is a PHP port of the original Markdown formatter written in Perl 2855 by John Gruber. This special "Extra" version of PHP Markdown features 2856 further enhancements to the syntax for making additional constructs 2878 This is a PHP port of the original Markdown formatter written in Perl 2879 by John Gruber. This special "Extra" version of PHP Markdown features 2880 further enhancements to the syntax for making additional constructs 2857 2881 such as tables and definition list. 2858 2882 … … 2884 2908 2885 2909 Version History 2886 --------------- 2910 --------------- 2887 2911 2888 2912 See the readme file for detailed release notes for this version. … … 2892 2916 --------------------- 2893 2917 2894 PHP Markdown & Extra 2895 Copyright (c) 2004-2009 Michel Fortin 2896 <http://michelf.com/> 2918 PHP Markdown & Extra 2919 Copyright (c) 2004-2009 Michel Fortin 2920 <http://michelf.com/> 2897 2921 All rights reserved. 2898 2922 2899 Based on Markdown 2900 Copyright (c) 2003-2006 John Gruber 2901 <http://daringfireball.net/> 2923 Based on Markdown 2924 Copyright (c) 2003-2006 John Gruber 2925 <http://daringfireball.net/> 2902 2926 All rights reserved. 2903 2927
Note: See TracChangeset
for help on using the changeset viewer.