Completed
Push — master ( c4cd1f...963c86 )
by
unknown
36s
created
uninstall.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -9,7 +9,7 @@
 block discarded – undo
9 9
 
10 10
 // If uninstall, not called from WordPress, then exit.
11 11
 if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
12
-	exit;
12
+    exit;
13 13
 }
14 14
 
15 15
 // TODO: Define uninstall functionality here.
Please login to merge, or discard this patch.
redux-core/inc/classes/class-redux-transients.php 1 patch
Indentation   +23 added lines, -23 removed lines patch added patch discarded remove patch
@@ -11,31 +11,31 @@
 block discarded – undo
11 11
 
12 12
 if ( ! class_exists( 'Redux_Transients', false ) ) {
13 13
 
14
-	/**
15
-	 * Class Redux_Transients
16
-	 */
17
-	class Redux_Transients extends Redux_Class {
14
+    /**
15
+     * Class Redux_Transients
16
+     */
17
+    class Redux_Transients extends Redux_Class {
18 18
 
19
-		/**
20
-		 * Get transients from database.
21
-		 */
22
-		public function get() {
23
-			$core = $this->core();
19
+        /**
20
+         * Get transients from database.
21
+         */
22
+        public function get() {
23
+            $core = $this->core();
24 24
 
25
-			if ( empty( $core->transients ) ) {
26
-				$core->transients = get_option( $core->args['opt_name'] . '-transients', array() );
27
-			}
28
-		}
25
+            if ( empty( $core->transients ) ) {
26
+                $core->transients = get_option( $core->args['opt_name'] . '-transients', array() );
27
+            }
28
+        }
29 29
 
30
-		/**
31
-		 * Set transients in database.
32
-		 */
33
-		public function set() {
34
-			$core = $this->core();
30
+        /**
31
+         * Set transients in database.
32
+         */
33
+        public function set() {
34
+            $core = $this->core();
35 35
 
36
-			if ( ! isset( $core->transients ) || ! isset( $core->transients_check ) || $core->transients_check !== $core->transients ) {
37
-				update_option( $core->args['opt_name'] . '-transients', $core->transients );
38
-			}
39
-		}
40
-	}
36
+            if ( ! isset( $core->transients ) || ! isset( $core->transients_check ) || $core->transients_check !== $core->transients ) {
37
+                update_option( $core->args['opt_name'] . '-transients', $core->transients );
38
+            }
39
+        }
40
+    }
41 41
 }
Please login to merge, or discard this patch.
redux-core/inc/fields/divide/class-redux-divide.php 1 patch
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -12,46 +12,46 @@
 block discarded – undo
12 12
 // Don't duplicate me!
13 13
 if ( ! class_exists( 'Redux_Divide', false ) ) {
14 14
 
15
-	/**
16
-	 * Main Redux_divide class
17
-	 *
18
-	 * @since       1.0.0
19
-	 */
20
-	class Redux_Divide extends Redux_Field {
15
+    /**
16
+     * Main Redux_divide class
17
+     *
18
+     * @since       1.0.0
19
+     */
20
+    class Redux_Divide extends Redux_Field {
21 21
 
22
-		/**
23
-		 * Field Render Function.
24
-		 * Takes the vars and outputs the HTML for the field in the settings
25
-		 *
26
-		 * @since         1.0.0
27
-		 * @access        public
28
-		 * @return        void
29
-		 */
30
-		public function render() {
31
-			echo '</td></tr></table>';
32
-			echo '<div data-id="' . esc_attr( $this->field['id'] ) . '" id="divide-' . esc_attr( $this->field['id'] ) . '" class="divide ' . esc_attr( $this->field['class'] ) . '"><div class="inner"><span>&nbsp;</span></div></div>';
33
-			echo '<table class="form-table no-border"><tbody><tr><th></th><td>';
34
-		}
22
+        /**
23
+         * Field Render Function.
24
+         * Takes the vars and outputs the HTML for the field in the settings
25
+         *
26
+         * @since         1.0.0
27
+         * @access        public
28
+         * @return        void
29
+         */
30
+        public function render() {
31
+            echo '</td></tr></table>';
32
+            echo '<div data-id="' . esc_attr( $this->field['id'] ) . '" id="divide-' . esc_attr( $this->field['id'] ) . '" class="divide ' . esc_attr( $this->field['class'] ) . '"><div class="inner"><span>&nbsp;</span></div></div>';
33
+            echo '<table class="form-table no-border"><tbody><tr><th></th><td>';
34
+        }
35 35
 
36
-		/**
37
-		 * Enqueue Function.
38
-		 * If this field requires any scripts, or css define this function and register/enqueue the scripts/css
39
-		 *
40
-		 * @since       1.0.0
41
-		 * @access      public
42
-		 * @return      void
43
-		 */
44
-		public function enqueue() {
45
-			if ( $this->parent->args['dev_mode'] ) {
46
-				wp_enqueue_style(
47
-					'redux-field-divide',
48
-					Redux_Core::$url . 'inc/fields/divide/redux-divide.css',
49
-					array(),
50
-					$this->timestamp
51
-				);
52
-			}
53
-		}
54
-	}
36
+        /**
37
+         * Enqueue Function.
38
+         * If this field requires any scripts, or css define this function and register/enqueue the scripts/css
39
+         *
40
+         * @since       1.0.0
41
+         * @access      public
42
+         * @return      void
43
+         */
44
+        public function enqueue() {
45
+            if ( $this->parent->args['dev_mode'] ) {
46
+                wp_enqueue_style(
47
+                    'redux-field-divide',
48
+                    Redux_Core::$url . 'inc/fields/divide/redux-divide.css',
49
+                    array(),
50
+                    $this->timestamp
51
+                );
52
+            }
53
+        }
54
+    }
55 55
 }
56 56
 
57 57
 class_alias( 'Redux_Divide', 'ReduxFramework_Divide' );
Please login to merge, or discard this patch.
redux-core/inc/fields/password/class-redux-password.php 1 patch
Indentation   +76 added lines, -76 removed lines patch added patch discarded remove patch
@@ -11,67 +11,67 @@  discard block
 block discarded – undo
11 11
 
12 12
 if ( ! class_exists( 'Redux_Password', false ) ) {
13 13
 
14
-	/**
15
-	 * Class Redux_Password
16
-	 */
17
-	class Redux_Password extends Redux_Field {
14
+    /**
15
+     * Class Redux_Password
16
+     */
17
+    class Redux_Password extends Redux_Field {
18 18
 
19
-		/**
20
-		 * Field Render Function.
21
-		 * Takes the vars and outputs the HTML for the field in the settings
22
-		 *
23
-		 * @since ReduxFramework 1.0.1
24
-		 */
25
-		public function render() {
26
-			if ( ! empty( $this->field['username'] ) && true === $this->field['username'] ) {
27
-				$this->render_combined_field();
28
-			} else {
29
-				$this->render_single_field();
30
-			}
31
-		}
19
+        /**
20
+         * Field Render Function.
21
+         * Takes the vars and outputs the HTML for the field in the settings
22
+         *
23
+         * @since ReduxFramework 1.0.1
24
+         */
25
+        public function render() {
26
+            if ( ! empty( $this->field['username'] ) && true === $this->field['username'] ) {
27
+                $this->render_combined_field();
28
+            } else {
29
+                $this->render_single_field();
30
+            }
31
+        }
32 32
 
33
-		/**
34
-		 * This will render a combined User/Password field
35
-		 *
36
-		 * @since ReduxFramework 3.0.9
37
-		 * @example
38
-		 *        <code>
39
-		 *        array(
40
-		 *        'id'          => 'smtp_account',
41
-		 *        'type'        => 'password',
42
-		 *        'username'    => true,
43
-		 *        'title'       => 'SMTP Account',
44
-		 *        'placeholder' => array('username' => 'Username')
45
-		 *        )
46
-		 *        </code>
47
-		 */
48
-		private function render_combined_field() {
49
-			$defaults = array(
50
-				'username'    => '',
51
-				'password'    => '',
52
-				'placeholder' => array(
53
-					'password' => esc_html__( 'Password', 'redux-framework' ),
54
-					'username' => esc_html__( 'Username', 'redux-framework' ),
55
-				),
56
-			);
33
+        /**
34
+         * This will render a combined User/Password field
35
+         *
36
+         * @since ReduxFramework 3.0.9
37
+         * @example
38
+         *        <code>
39
+         *        array(
40
+         *        'id'          => 'smtp_account',
41
+         *        'type'        => 'password',
42
+         *        'username'    => true,
43
+         *        'title'       => 'SMTP Account',
44
+         *        'placeholder' => array('username' => 'Username')
45
+         *        )
46
+         *        </code>
47
+         */
48
+        private function render_combined_field() {
49
+            $defaults = array(
50
+                'username'    => '',
51
+                'password'    => '',
52
+                'placeholder' => array(
53
+                    'password' => esc_html__( 'Password', 'redux-framework' ),
54
+                    'username' => esc_html__( 'Username', 'redux-framework' ),
55
+                ),
56
+            );
57 57
 
58
-			$this->value = wp_parse_args( $this->value, $defaults );
58
+            $this->value = wp_parse_args( $this->value, $defaults );
59 59
 
60
-			if ( ! empty( $this->field['placeholder'] ) ) {
61
-				if ( is_array( $this->field['placeholder'] ) ) {
62
-					if ( ! empty( $this->field['placeholder']['password'] ) ) {
63
-						$this->value['placeholder']['password'] = $this->field['placeholder']['password'];
64
-					}
65
-					if ( ! empty( $this->field['placeholder']['username'] ) ) {
66
-						$this->value['placeholder']['username'] = $this->field['placeholder']['username'];
67
-					}
68
-				} else {
69
-					$this->value['placeholder']['password'] = $this->field['placeholder'];
70
-				}
71
-			}
60
+            if ( ! empty( $this->field['placeholder'] ) ) {
61
+                if ( is_array( $this->field['placeholder'] ) ) {
62
+                    if ( ! empty( $this->field['placeholder']['password'] ) ) {
63
+                        $this->value['placeholder']['password'] = $this->field['placeholder']['password'];
64
+                    }
65
+                    if ( ! empty( $this->field['placeholder']['username'] ) ) {
66
+                        $this->value['placeholder']['username'] = $this->field['placeholder']['username'];
67
+                    }
68
+                } else {
69
+                    $this->value['placeholder']['password'] = $this->field['placeholder'];
70
+                }
71
+            }
72 72
 
73
-			// Username field.
74
-			echo '<input 
73
+            // Username field.
74
+            echo '<input 
75 75
 					type="text" 
76 76
 					autocomplete="off" 
77 77
 					placeholder="' . esc_attr( $this->value['placeholder']['username'] ) . '" 
@@ -80,8 +80,8 @@  discard block
 block discarded – undo
80 80
 					value="' . esc_attr( $this->value['username'] ) . '" 
81 81
 					class="' . esc_attr( $this->field['class'] ) . '" />&nbsp;';
82 82
 
83
-			// Password field.
84
-			echo '<input 
83
+            // Password field.
84
+            echo '<input 
85 85
 					type="password" 
86 86
 					autocomplete="off" 
87 87
 					placeholder="' . esc_attr( $this->value['placeholder']['password'] ) . '" 
@@ -89,30 +89,30 @@  discard block
 block discarded – undo
89 89
 					name="' . esc_attr( $this->field['name'] . $this->field['name_suffix'] ) . '[password]" 
90 90
 					value="' . esc_attr( $this->value['password'] ) . '" 
91 91
 					class="' . esc_attr( $this->field['class'] ) . '" />';
92
-		}
92
+        }
93 93
 
94
-		/**
95
-		 * This will render a single Password field
96
-		 *
97
-		 * @since ReduxFramework 3.0.9
98
-		 * @example
99
-		 *        <code>
100
-		 *        array(
101
-		 *        'id'    => 'smtp_password',
102
-		 *        'type'  => 'password',
103
-		 *        'title' => 'SMTP Password'
104
-		 *        )
105
-		 *        </code>
106
-		 */
107
-		private function render_single_field() {
108
-			echo '<input 
94
+        /**
95
+         * This will render a single Password field
96
+         *
97
+         * @since ReduxFramework 3.0.9
98
+         * @example
99
+         *        <code>
100
+         *        array(
101
+         *        'id'    => 'smtp_password',
102
+         *        'type'  => 'password',
103
+         *        'title' => 'SMTP Password'
104
+         *        )
105
+         *        </code>
106
+         */
107
+        private function render_single_field() {
108
+            echo '<input 
109 109
 					type="password" 
110 110
 					id="' . esc_attr( $this->field['id'] ) . '" 
111 111
 					name="' . esc_attr( $this->field['name'] . $this->field['name_suffix'] ) . '" 
112 112
 					value="' . esc_attr( $this->value ) . '" 
113 113
 					class="' . esc_attr( $this->field['class'] ) . '" />';
114
-		}
115
-	}
114
+        }
115
+    }
116 116
 }
117 117
 
118 118
 class_alias( 'Redux_Password', 'ReduxFramework_Password' );
Please login to merge, or discard this patch.
inc/validation/preg_replace/class-redux-validation-preg-replace.php 1 patch
Indentation   +17 added lines, -17 removed lines patch added patch discarded remove patch
@@ -12,23 +12,23 @@
 block discarded – undo
12 12
 
13 13
 if ( ! class_exists( 'Redux_Validation_Preg_Replace', false ) ) {
14 14
 
15
-	/**
16
-	 * Class Redux_Validation_Preg_Replace
17
-	 */
18
-	class Redux_Validation_Preg_Replace extends Redux_Validate {
15
+    /**
16
+     * Class Redux_Validation_Preg_Replace
17
+     */
18
+    class Redux_Validation_Preg_Replace extends Redux_Validate {
19 19
 
20
-		/**
21
-		 * Field Validate Function.
22
-		 * Takes the vars and validates them
23
-		 *
24
-		 * @since ReduxFramework 1.0.0
25
-		 */
26
-		public function validate() {
27
-			$that                   = $this;
28
-			$this->value            = preg_replace( $this->field['preg']['pattern'], $that->field['preg']['replacement'], $this->value );
29
-			$this->field['current'] = $this->value;
20
+        /**
21
+         * Field Validate Function.
22
+         * Takes the vars and validates them
23
+         *
24
+         * @since ReduxFramework 1.0.0
25
+         */
26
+        public function validate() {
27
+            $that                   = $this;
28
+            $this->value            = preg_replace( $this->field['preg']['pattern'], $that->field['preg']['replacement'], $this->value );
29
+            $this->field['current'] = $this->value;
30 30
 
31
-			$this->sanitize = $this->field;
32
-		}
33
-	}
31
+            $this->sanitize = $this->field;
32
+        }
33
+    }
34 34
 }
Please login to merge, or discard this patch.
inc/validation/str_replace/class-redux-validation-str-replace.php 1 patch
Indentation   +16 added lines, -16 removed lines patch added patch discarded remove patch
@@ -12,22 +12,22 @@
 block discarded – undo
12 12
 
13 13
 if ( ! class_exists( 'Redux_Validation_Str_Replace', false ) ) {
14 14
 
15
-	/**
16
-	 * Class Redux_Validation_Str_Replace
17
-	 */
18
-	class Redux_Validation_Str_Replace extends Redux_Validate {
15
+    /**
16
+     * Class Redux_Validation_Str_Replace
17
+     */
18
+    class Redux_Validation_Str_Replace extends Redux_Validate {
19 19
 
20
-		/**
21
-		 * Field Validate Function.
22
-		 * Takes the vars and validates them
23
-		 *
24
-		 * @since ReduxFramework 1.0.0
25
-		 */
26
-		public function validate() {
27
-			$this->value = str_replace( $this->field['str']['search'], $this->field['str']['replacement'], $this->value );
20
+        /**
21
+         * Field Validate Function.
22
+         * Takes the vars and validates them
23
+         *
24
+         * @since ReduxFramework 1.0.0
25
+         */
26
+        public function validate() {
27
+            $this->value = str_replace( $this->field['str']['search'], $this->field['str']['replacement'], $this->value );
28 28
 
29
-			$this->field['current'] = $this->value;
30
-			$this->sanitize         = $this->field;
31
-		}
32
-	}
29
+            $this->field['current'] = $this->value;
30
+            $this->sanitize         = $this->field;
31
+        }
32
+    }
33 33
 }
Please login to merge, or discard this patch.
redux-core/templates/panel/content.tpl.php 1 patch
Indentation   +31 added lines, -31 removed lines patch added patch discarded remove patch
@@ -49,41 +49,41 @@
 block discarded – undo
49 49
 			<?php } ?>
50 50
 
51 51
 			<?php
52
-			if ( $display ) {
53
-				/**
54
-				 * Action 'redux/page/{opt_name}/section/before'
55
-				 *
56
-				 * @param object $this ReduxFramework
57
-				 */
58
-
59
-				// phpcs:ignore WordPress.NamingConventions.ValidHookName
60
-				do_action( "redux/page/{$this->parent->args['opt_name']}/section/before", $section );
61
-
62
-				$this->output_section( $k );
63
-
64
-				/**
65
-				 * Action 'redux/page/{opt_name}/section/after'
66
-				 *
67
-				 * @param object $this ReduxFramework
68
-				 */
69
-
70
-				// phpcs:ignore WordPress.NamingConventions.ValidHookName
71
-				do_action( "redux/page/{$this->parent->args['opt_name']}/section/after", $section );
72
-			}
73
-			?>
52
+            if ( $display ) {
53
+                /**
54
+                 * Action 'redux/page/{opt_name}/section/before'
55
+                 *
56
+                 * @param object $this ReduxFramework
57
+                 */
58
+
59
+                // phpcs:ignore WordPress.NamingConventions.ValidHookName
60
+                do_action( "redux/page/{$this->parent->args['opt_name']}/section/before", $section );
61
+
62
+                $this->output_section( $k );
63
+
64
+                /**
65
+                 * Action 'redux/page/{opt_name}/section/after'
66
+                 *
67
+                 * @param object $this ReduxFramework
68
+                 */
69
+
70
+                // phpcs:ignore WordPress.NamingConventions.ValidHookName
71
+                do_action( "redux/page/{$this->parent->args['opt_name']}/section/after", $section );
72
+            }
73
+            ?>
74 74
 		</div> <!-- section group -->
75 75
 	<?php } ?>
76 76
 
77 77
 	<?php
78
-	/**
79
-	 * Action 'redux/page/{opt_name}/sections/after'
80
-	 *
81
-	 * @param object $this ReduxFramework
82
-	 */
83
-
84
-	// phpcs:ignore WordPress.NamingConventions.ValidHookName
85
-	do_action( "redux/page/{$this->parent->args['opt_name']}/sections/after", $this );
86
-	?>
78
+    /**
79
+     * Action 'redux/page/{opt_name}/sections/after'
80
+     *
81
+     * @param object $this ReduxFramework
82
+     */
83
+
84
+    // phpcs:ignore WordPress.NamingConventions.ValidHookName
85
+    do_action( "redux/page/{$this->parent->args['opt_name']}/sections/after", $this );
86
+    ?>
87 87
 	<div class="clear"></div>
88 88
 	<!-- Footer Block -->
89 89
 	<?php $this->get_template( 'footer.tpl.php' ); ?>
Please login to merge, or discard this patch.
redux-core/templates/panel/header.tpl.php 1 patch
Indentation   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -11,29 +11,29 @@
 block discarded – undo
11 11
 $tip_title = esc_html__( 'Developer Mode Enabled', 'redux-framework' );
12 12
 
13 13
 if ( $this->parent->args_class->dev_mode_forced ) {
14
-	$is_debug     = false;
15
-	$is_localhost = false;
16
-	$debug_bit    = '';
14
+    $is_debug     = false;
15
+    $is_localhost = false;
16
+    $debug_bit    = '';
17 17
 
18
-	if ( Redux_Helpers::is_wp_debug() ) {
19
-		$is_debug  = true;
20
-		$debug_bit = esc_html__( 'WP_DEBUG is enabled', 'redux-framework' );
21
-	}
18
+    if ( Redux_Helpers::is_wp_debug() ) {
19
+        $is_debug  = true;
20
+        $debug_bit = esc_html__( 'WP_DEBUG is enabled', 'redux-framework' );
21
+    }
22 22
 
23
-	$localhost_bit = '';
24
-	if ( Redux_Helpers::is_local_host() ) {
25
-		$is_localhost  = true;
26
-		$localhost_bit = esc_html__( 'you are working in a localhost environment', 'redux-framework' );
27
-	}
23
+    $localhost_bit = '';
24
+    if ( Redux_Helpers::is_local_host() ) {
25
+        $is_localhost  = true;
26
+        $localhost_bit = esc_html__( 'you are working in a localhost environment', 'redux-framework' );
27
+    }
28 28
 
29
-	$conjunction_bit = '';
30
-	if ( $is_localhost && $is_debug ) {
31
-		$conjunction_bit = ' ' . esc_html__( 'and', 'redux-framework' ) . ' ';
32
-	}
29
+    $conjunction_bit = '';
30
+    if ( $is_localhost && $is_debug ) {
31
+        $conjunction_bit = ' ' . esc_html__( 'and', 'redux-framework' ) . ' ';
32
+    }
33 33
 
34
-	$tip_msg = esc_html__( 'This has been automatically enabled because', 'redux-framework' ) . ' ' . $debug_bit . $conjunction_bit . $localhost_bit . '.';
34
+    $tip_msg = esc_html__( 'This has been automatically enabled because', 'redux-framework' ) . ' ' . $debug_bit . $conjunction_bit . $localhost_bit . '.';
35 35
 } else {
36
-	$tip_msg = esc_html__( 'If you are not a developer, your theme/plugin author shipped with developer mode enabled. Contact them directly to fix it.', 'redux-framework' );
36
+    $tip_msg = esc_html__( 'If you are not a developer, your theme/plugin author shipped with developer mode enabled. Contact them directly to fix it.', 'redux-framework' );
37 37
 }
38 38
 
39 39
 ?>
Please login to merge, or discard this patch.
redux-core/inc/fields/raw/parsedown.php 1 patch
Indentation   +1960 added lines, -1960 removed lines patch added patch discarded remove patch
@@ -14,1983 +14,1983 @@
 block discarded – undo
14 14
 #
15 15
 
16 16
 if (! class_exists( 'Redux_Parsedown' ) ) {
17
-	class Redux_Parsedown
18
-	{
19
-		# ~
17
+    class Redux_Parsedown
18
+    {
19
+        # ~
20 20
 
21
-		const version = '1.8.0-beta-7';
21
+        const version = '1.8.0-beta-7';
22 22
 
23
-		# ~
23
+        # ~
24 24
 
25
-		function text($text)
26
-		{
27
-			$Elements = $this->textElements($text);
25
+        function text($text)
26
+        {
27
+            $Elements = $this->textElements($text);
28 28
 
29
-			# convert to markup
30
-			$markup = $this->elements($Elements);
29
+            # convert to markup
30
+            $markup = $this->elements($Elements);
31 31
 
32
-			# trim line breaks
33
-			$markup = trim($markup, "\n");
32
+            # trim line breaks
33
+            $markup = trim($markup, "\n");
34 34
 
35
-			return $markup;
36
-		}
35
+            return $markup;
36
+        }
37 37
 
38
-		protected function textElements($text)
39
-		{
40
-			# make sure no definitions are set
41
-			$this->DefinitionData = array();
42
-
43
-			# standardize line breaks
44
-			$text = str_replace(array("\r\n", "\r"), "\n", $text);
45
-
46
-			# remove surrounding line breaks
47
-			$text = trim($text, "\n");
48
-
49
-			# split text into lines
50
-			$lines = explode("\n", $text);
51
-
52
-			# iterate through lines to identify blocks
53
-			return $this->linesElements($lines);
54
-		}
55
-
56
-		#
57
-		# Setters
58
-		#
59
-
60
-		function setBreaksEnabled($breaksEnabled)
61
-		{
62
-			$this->breaksEnabled = $breaksEnabled;
63
-
64
-			return $this;
65
-		}
66
-
67
-		protected $breaksEnabled;
68
-
69
-		function setMarkupEscaped($markupEscaped)
70
-		{
71
-			$this->markupEscaped = $markupEscaped;
72
-
73
-			return $this;
74
-		}
75
-
76
-		protected $markupEscaped;
77
-
78
-		function setUrlsLinked($urlsLinked)
79
-		{
80
-			$this->urlsLinked = $urlsLinked;
81
-
82
-			return $this;
83
-		}
84
-
85
-		protected $urlsLinked = true;
86
-
87
-		function setSafeMode($safeMode)
88
-		{
89
-			$this->safeMode = (bool) $safeMode;
90
-
91
-			return $this;
92
-		}
93
-
94
-		protected $safeMode;
95
-
96
-		function setStrictMode($strictMode)
97
-		{
98
-			$this->strictMode = (bool) $strictMode;
99
-
100
-			return $this;
101
-		}
102
-
103
-		protected $strictMode;
104
-
105
-		protected $safeLinksWhitelist = array(
106
-			'http://',
107
-			'https://',
108
-			'ftp://',
109
-			'ftps://',
110
-			'mailto:',
111
-			'tel:',
112
-			'data:image/png;base64,',
113
-			'data:image/gif;base64,',
114
-			'data:image/jpeg;base64,',
115
-			'irc:',
116
-			'ircs:',
117
-			'git:',
118
-			'ssh:',
119
-			'news:',
120
-			'steam:',
121
-		);
122
-
123
-		#
124
-		# Lines
125
-		#
126
-
127
-		protected $BlockTypes = array(
128
-			'#' => array('Header'),
129
-			'*' => array('Rule', 'List'),
130
-			'+' => array('List'),
131
-			'-' => array('SetextHeader', 'Table', 'Rule', 'List'),
132
-			'0' => array('List'),
133
-			'1' => array('List'),
134
-			'2' => array('List'),
135
-			'3' => array('List'),
136
-			'4' => array('List'),
137
-			'5' => array('List'),
138
-			'6' => array('List'),
139
-			'7' => array('List'),
140
-			'8' => array('List'),
141
-			'9' => array('List'),
142
-			':' => array('Table'),
143
-			'<' => array('Comment', 'Markup'),
144
-			'=' => array('SetextHeader'),
145
-			'>' => array('Quote'),
146
-			'[' => array('Reference'),
147
-			'_' => array('Rule'),
148
-			'`' => array('FencedCode'),
149
-			'|' => array('Table'),
150
-			'~' => array('FencedCode'),
151
-		);
152
-
153
-		# ~
154
-
155
-		protected $unmarkedBlockTypes = array(
156
-			'Code',
157
-		);
158
-
159
-		#
160
-		# Blocks
161
-		#
162
-
163
-		protected function lines(array $lines)
164
-		{
165
-			return $this->elements($this->linesElements($lines));
166
-		}
167
-
168
-		protected function linesElements(array $lines)
169
-		{
170
-			$Elements = array();
171
-			$CurrentBlock = null;
172
-
173
-			foreach ($lines as $line)
174
-			{
175
-				if (chop($line) === '')
176
-				{
177
-					if (isset($CurrentBlock))
178
-					{
179
-						$CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
180
-							? $CurrentBlock['interrupted'] + 1 : 1
181
-						);
182
-					}
183
-
184
-					continue;
185
-				}
186
-
187
-				while (($beforeTab = strstr($line, "\t", true)) !== false)
188
-				{
189
-					$shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
190
-
191
-					$line = $beforeTab
192
-					        . str_repeat(' ', $shortage)
193
-					        . substr($line, strlen($beforeTab) + 1)
194
-					;
195
-				}
196
-
197
-				$indent = strspn($line, ' ');
198
-
199
-				$text = $indent > 0 ? substr($line, $indent) : $line;
200
-
201
-				# ~
202
-
203
-				$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
204
-
205
-				# ~
206
-
207
-				if (isset($CurrentBlock['continuable']))
208
-				{
209
-					$methodName = 'block' . $CurrentBlock['type'] . 'Continue';
210
-					$Block = $this->$methodName($Line, $CurrentBlock);
211
-
212
-					if (isset($Block))
213
-					{
214
-						$CurrentBlock = $Block;
215
-
216
-						continue;
217
-					}
218
-					else
219
-					{
220
-						if ($this->isBlockCompletable($CurrentBlock['type']))
221
-						{
222
-							$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
223
-							$CurrentBlock = $this->$methodName($CurrentBlock);
224
-						}
225
-					}
226
-				}
227
-
228
-				# ~
229
-
230
-				$marker = $text[0];
231
-
232
-				# ~
233
-
234
-				$blockTypes = $this->unmarkedBlockTypes;
235
-
236
-				if (isset($this->BlockTypes[$marker]))
237
-				{
238
-					foreach ($this->BlockTypes[$marker] as $blockType)
239
-					{
240
-						$blockTypes []= $blockType;
241
-					}
242
-				}
243
-
244
-				#
245
-				# ~
246
-
247
-				foreach ($blockTypes as $blockType)
248
-				{
249
-					$Block = $this->{"block$blockType"}($Line, $CurrentBlock);
250
-
251
-					if (isset($Block))
252
-					{
253
-						$Block['type'] = $blockType;
254
-
255
-						if ( ! isset($Block['identified']))
256
-						{
257
-							if (isset($CurrentBlock))
258
-							{
259
-								$Elements[] = $this->extractElement($CurrentBlock);
260
-							}
261
-
262
-							$Block['identified'] = true;
263
-						}
264
-
265
-						if ($this->isBlockContinuable($blockType))
266
-						{
267
-							$Block['continuable'] = true;
268
-						}
269
-
270
-						$CurrentBlock = $Block;
271
-
272
-						continue 2;
273
-					}
274
-				}
275
-
276
-				# ~
277
-
278
-				if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
279
-				{
280
-					$Block = $this->paragraphContinue($Line, $CurrentBlock);
281
-				}
282
-
283
-				if (isset($Block))
284
-				{
285
-					$CurrentBlock = $Block;
286
-				}
287
-				else
288
-				{
289
-					if (isset($CurrentBlock))
290
-					{
291
-						$Elements[] = $this->extractElement($CurrentBlock);
292
-					}
293
-
294
-					$CurrentBlock = $this->paragraph($Line);
295
-
296
-					$CurrentBlock['identified'] = true;
297
-				}
298
-			}
299
-
300
-			# ~
301
-
302
-			if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
303
-			{
304
-				$methodName = 'block' . $CurrentBlock['type'] . 'Complete';
305
-				$CurrentBlock = $this->$methodName($CurrentBlock);
306
-			}
307
-
308
-			# ~
309
-
310
-			if (isset($CurrentBlock))
311
-			{
312
-				$Elements[] = $this->extractElement($CurrentBlock);
313
-			}
314
-
315
-			# ~
316
-
317
-			return $Elements;
318
-		}
319
-
320
-		protected function extractElement(array $Component)
321
-		{
322
-			if ( ! isset($Component['element']))
323
-			{
324
-				if (isset($Component['markup']))
325
-				{
326
-					$Component['element'] = array('rawHtml' => $Component['markup']);
327
-				}
328
-				elseif (isset($Component['hidden']))
329
-				{
330
-					$Component['element'] = array();
331
-				}
332
-			}
333
-
334
-			return $Component['element'];
335
-		}
336
-
337
-		protected function isBlockContinuable($Type)
338
-		{
339
-			return method_exists($this, 'block' . $Type . 'Continue');
340
-		}
341
-
342
-		protected function isBlockCompletable($Type)
343
-		{
344
-			return method_exists($this, 'block' . $Type . 'Complete');
345
-		}
346
-
347
-		#
348
-		# Code
349
-
350
-		protected function blockCode($Line, $Block = null)
351
-		{
352
-			if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
353
-			{
354
-				return;
355
-			}
356
-
357
-			if ($Line['indent'] >= 4)
358
-			{
359
-				$text = substr($Line['body'], 4);
360
-
361
-				$Block = array(
362
-					'element' => array(
363
-						'name' => 'pre',
364
-						'element' => array(
365
-							'name' => 'code',
366
-							'text' => $text,
367
-						),
368
-					),
369
-				);
370
-
371
-				return $Block;
372
-			}
373
-		}
374
-
375
-		protected function blockCodeContinue($Line, $Block)
376
-		{
377
-			if ($Line['indent'] >= 4)
378
-			{
379
-				if (isset($Block['interrupted']))
380
-				{
381
-					$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
382
-
383
-					unset($Block['interrupted']);
384
-				}
385
-
386
-				$Block['element']['element']['text'] .= "\n";
387
-
388
-				$text = substr($Line['body'], 4);
389
-
390
-				$Block['element']['element']['text'] .= $text;
391
-
392
-				return $Block;
393
-			}
394
-		}
395
-
396
-		protected function blockCodeComplete($Block)
397
-		{
398
-			return $Block;
399
-		}
400
-
401
-		#
402
-		# Comment
403
-
404
-		protected function blockComment($Line)
405
-		{
406
-			if ($this->markupEscaped or $this->safeMode)
407
-			{
408
-				return;
409
-			}
410
-
411
-			if (strpos($Line['text'], '<!--') === 0)
412
-			{
413
-				$Block = array(
414
-					'element' => array(
415
-						'rawHtml' => $Line['body'],
416
-						'autobreak' => true,
417
-					),
418
-				);
419
-
420
-				if (strpos($Line['text'], '-->') !== false)
421
-				{
422
-					$Block['closed'] = true;
423
-				}
424
-
425
-				return $Block;
426
-			}
427
-		}
428
-
429
-		protected function blockCommentContinue($Line, array $Block)
430
-		{
431
-			if (isset($Block['closed']))
432
-			{
433
-				return;
434
-			}
435
-
436
-			$Block['element']['rawHtml'] .= "\n" . $Line['body'];
437
-
438
-			if (strpos($Line['text'], '-->') !== false)
439
-			{
440
-				$Block['closed'] = true;
441
-			}
442
-
443
-			return $Block;
444
-		}
445
-
446
-		#
447
-		# Fenced Code
448
-
449
-		protected function blockFencedCode($Line)
450
-		{
451
-			$marker = $Line['text'][0];
452
-
453
-			$openerLength = strspn($Line['text'], $marker);
454
-
455
-			if ($openerLength < 3)
456
-			{
457
-				return;
458
-			}
459
-
460
-			$infostring = trim(substr($Line['text'], $openerLength), "\t ");
461
-
462
-			if (strpos($infostring, '`') !== false)
463
-			{
464
-				return;
465
-			}
466
-
467
-			$Element = array(
468
-				'name' => 'code',
469
-				'text' => '',
470
-			);
471
-
472
-			if ($infostring !== '')
473
-			{
474
-				/**
475
-				 * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
476
-				 * Every HTML element may have a class attribute specified.
477
-				 * The attribute, if specified, must have a value that is a set
478
-				 * of space-separated tokens representing the various classes
479
-				 * that the element belongs to.
480
-				 * [...]
481
-				 * The space characters, for the purposes of this specification,
482
-				 * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
483
-				 * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
484
-				 * U+000D CARRIAGE RETURN (CR).
485
-				 */
486
-				$language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
487
-
488
-				$Element['attributes'] = array('class' => "language-$language");
489
-			}
490
-
491
-			$Block = array(
492
-				'char' => $marker,
493
-				'openerLength' => $openerLength,
494
-				'element' => array(
495
-					'name' => 'pre',
496
-					'element' => $Element,
497
-				),
498
-			);
499
-
500
-			return $Block;
501
-		}
502
-
503
-		protected function blockFencedCodeContinue($Line, $Block)
504
-		{
505
-			if (isset($Block['complete']))
506
-			{
507
-				return;
508
-			}
509
-
510
-			if (isset($Block['interrupted']))
511
-			{
512
-				$Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
513
-
514
-				unset($Block['interrupted']);
515
-			}
516
-
517
-			if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
518
-			    and chop(substr($Line['text'], $len), ' ') === ''
519
-			) {
520
-				$Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
521
-
522
-				$Block['complete'] = true;
523
-
524
-				return $Block;
525
-			}
526
-
527
-			$Block['element']['element']['text'] .= "\n" . $Line['body'];
528
-
529
-			return $Block;
530
-		}
531
-
532
-		protected function blockFencedCodeComplete($Block)
533
-		{
534
-			return $Block;
535
-		}
536
-
537
-		#
538
-		# Header
539
-
540
-		protected function blockHeader($Line)
541
-		{
542
-			$level = strspn($Line['text'], '#');
543
-
544
-			if ($level > 6)
545
-			{
546
-				return;
547
-			}
548
-
549
-			$text = trim($Line['text'], '#');
550
-
551
-			if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
552
-			{
553
-				return;
554
-			}
555
-
556
-			$text = trim($text, ' ');
557
-
558
-			$Block = array(
559
-				'element' => array(
560
-					'name' => 'h' . $level,
561
-					'handler' => array(
562
-						'function' => 'lineElements',
563
-						'argument' => $text,
564
-						'destination' => 'elements',
565
-					)
566
-				),
567
-			);
568
-
569
-			return $Block;
570
-		}
571
-
572
-		#
573
-		# List
574
-
575
-		protected function blockList($Line, array $CurrentBlock = null)
576
-		{
577
-			list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
578
-
579
-			if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
580
-			{
581
-				$contentIndent = strlen($matches[2]);
582
-
583
-				if ($contentIndent >= 5)
584
-				{
585
-					$contentIndent -= 1;
586
-					$matches[1] = substr($matches[1], 0, -$contentIndent);
587
-					$matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
588
-				}
589
-				elseif ($contentIndent === 0)
590
-				{
591
-					$matches[1] .= ' ';
592
-				}
593
-
594
-				$markerWithoutWhitespace = strstr($matches[1], ' ', true);
595
-
596
-				$Block = array(
597
-					'indent' => $Line['indent'],
598
-					'pattern' => $pattern,
599
-					'data' => array(
600
-						'type' => $name,
601
-						'marker' => $matches[1],
602
-						'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
603
-					),
604
-					'element' => array(
605
-						'name' => $name,
606
-						'elements' => array(),
607
-					),
608
-				);
609
-				$Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
610
-
611
-				if ($name === 'ol')
612
-				{
613
-					$listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
614
-
615
-					if ($listStart !== '1')
616
-					{
617
-						if (
618
-							isset($CurrentBlock)
619
-							and $CurrentBlock['type'] === 'Paragraph'
620
-							    and ! isset($CurrentBlock['interrupted'])
621
-						) {
622
-							return;
623
-						}
624
-
625
-						$Block['element']['attributes'] = array('start' => $listStart);
626
-					}
627
-				}
628
-
629
-				$Block['li'] = array(
630
-					'name' => 'li',
631
-					'handler' => array(
632
-						'function' => 'li',
633
-						'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
634
-						'destination' => 'elements'
635
-					)
636
-				);
637
-
638
-				$Block['element']['elements'] []= & $Block['li'];
639
-
640
-				return $Block;
641
-			}
642
-		}
643
-
644
-		protected function blockListContinue($Line, array $Block)
645
-		{
646
-			if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
647
-			{
648
-				return null;
649
-			}
650
-
651
-			$requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
652
-
653
-			if ($Line['indent'] < $requiredIndent
654
-			    and (
655
-				    (
656
-					    $Block['data']['type'] === 'ol'
657
-					    and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
658
-				    ) or (
659
-					    $Block['data']['type'] === 'ul'
660
-					    and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
661
-				    )
662
-			    )
663
-			) {
664
-				if (isset($Block['interrupted']))
665
-				{
666
-					$Block['li']['handler']['argument'] []= '';
667
-
668
-					$Block['loose'] = true;
669
-
670
-					unset($Block['interrupted']);
671
-				}
672
-
673
-				unset($Block['li']);
674
-
675
-				$text = isset($matches[1]) ? $matches[1] : '';
676
-
677
-				$Block['indent'] = $Line['indent'];
678
-
679
-				$Block['li'] = array(
680
-					'name' => 'li',
681
-					'handler' => array(
682
-						'function' => 'li',
683
-						'argument' => array($text),
684
-						'destination' => 'elements'
685
-					)
686
-				);
687
-
688
-				$Block['element']['elements'] []= & $Block['li'];
689
-
690
-				return $Block;
691
-			}
692
-			elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
693
-			{
694
-				return null;
695
-			}
696
-
697
-			if ($Line['text'][0] === '[' and $this->blockReference($Line))
698
-			{
699
-				return $Block;
700
-			}
701
-
702
-			if ($Line['indent'] >= $requiredIndent)
703
-			{
704
-				if (isset($Block['interrupted']))
705
-				{
706
-					$Block['li']['handler']['argument'] []= '';
707
-
708
-					$Block['loose'] = true;
709
-
710
-					unset($Block['interrupted']);
711
-				}
712
-
713
-				$text = substr($Line['body'], $requiredIndent);
714
-
715
-				$Block['li']['handler']['argument'] []= $text;
716
-
717
-				return $Block;
718
-			}
719
-
720
-			if ( ! isset($Block['interrupted']))
721
-			{
722
-				$text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
723
-
724
-				$Block['li']['handler']['argument'] []= $text;
725
-
726
-				return $Block;
727
-			}
728
-		}
729
-
730
-		protected function blockListComplete(array $Block)
731
-		{
732
-			if (isset($Block['loose']))
733
-			{
734
-				foreach ($Block['element']['elements'] as &$li)
735
-				{
736
-					if (end($li['handler']['argument']) !== '')
737
-					{
738
-						$li['handler']['argument'] []= '';
739
-					}
740
-				}
741
-			}
742
-
743
-			return $Block;
744
-		}
745
-
746
-		#
747
-		# Quote
748
-
749
-		protected function blockQuote($Line)
750
-		{
751
-			if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
752
-			{
753
-				$Block = array(
754
-					'element' => array(
755
-						'name' => 'blockquote',
756
-						'handler' => array(
757
-							'function' => 'linesElements',
758
-							'argument' => (array) $matches[1],
759
-							'destination' => 'elements',
760
-						)
761
-					),
762
-				);
763
-
764
-				return $Block;
765
-			}
766
-		}
767
-
768
-		protected function blockQuoteContinue($Line, array $Block)
769
-		{
770
-			if (isset($Block['interrupted']))
771
-			{
772
-				return;
773
-			}
774
-
775
-			if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
776
-			{
777
-				$Block['element']['handler']['argument'] []= $matches[1];
778
-
779
-				return $Block;
780
-			}
781
-
782
-			if ( ! isset($Block['interrupted']))
783
-			{
784
-				$Block['element']['handler']['argument'] []= $Line['text'];
785
-
786
-				return $Block;
787
-			}
788
-		}
789
-
790
-		#
791
-		# Rule
792
-
793
-		protected function blockRule($Line)
794
-		{
795
-			$marker = $Line['text'][0];
796
-
797
-			if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
798
-			{
799
-				$Block = array(
800
-					'element' => array(
801
-						'name' => 'hr',
802
-					),
803
-				);
804
-
805
-				return $Block;
806
-			}
807
-		}
808
-
809
-		#
810
-		# Setext
811
-
812
-		protected function blockSetextHeader($Line, array $Block = null)
813
-		{
814
-			if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
815
-			{
816
-				return;
817
-			}
818
-
819
-			if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
820
-			{
821
-				$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
822
-
823
-				return $Block;
824
-			}
825
-		}
826
-
827
-		#
828
-		# Markup
829
-
830
-		protected function blockMarkup($Line)
831
-		{
832
-			if ($this->markupEscaped or $this->safeMode)
833
-			{
834
-				return;
835
-			}
836
-
837
-			if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
838
-			{
839
-				$element = strtolower($matches[1]);
840
-
841
-				if (in_array($element, $this->textLevelElements))
842
-				{
843
-					return;
844
-				}
845
-
846
-				$Block = array(
847
-					'name' => $matches[1],
848
-					'element' => array(
849
-						'rawHtml' => $Line['text'],
850
-						'autobreak' => true,
851
-					),
852
-				);
853
-
854
-				return $Block;
855
-			}
856
-		}
857
-
858
-		protected function blockMarkupContinue($Line, array $Block)
859
-		{
860
-			if (isset($Block['closed']) or isset($Block['interrupted']))
861
-			{
862
-				return;
863
-			}
864
-
865
-			$Block['element']['rawHtml'] .= "\n" . $Line['body'];
866
-
867
-			return $Block;
868
-		}
869
-
870
-		#
871
-		# Reference
872
-
873
-		protected function blockReference($Line)
874
-		{
875
-			if (strpos($Line['text'], ']') !== false
876
-			    and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
877
-			) {
878
-				$id = strtolower($matches[1]);
879
-
880
-				$Data = array(
881
-					'url' => $matches[2],
882
-					'title' => isset($matches[3]) ? $matches[3] : null,
883
-				);
884
-
885
-				$this->DefinitionData['Reference'][$id] = $Data;
886
-
887
-				$Block = array(
888
-					'element' => array(),
889
-				);
890
-
891
-				return $Block;
892
-			}
893
-		}
894
-
895
-		#
896
-		# Table
897
-
898
-		protected function blockTable($Line, array $Block = null)
899
-		{
900
-			if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
901
-			{
902
-				return;
903
-			}
904
-
905
-			if (
906
-				strpos($Block['element']['handler']['argument'], '|') === false
907
-				and strpos($Line['text'], '|') === false
908
-				    and strpos($Line['text'], ':') === false
909
-				or strpos($Block['element']['handler']['argument'], "\n") !== false
910
-			) {
911
-				return;
912
-			}
913
-
914
-			if (chop($Line['text'], ' -:|') !== '')
915
-			{
916
-				return;
917
-			}
918
-
919
-			$alignments = array();
920
-
921
-			$divider = $Line['text'];
922
-
923
-			$divider = trim($divider);
924
-			$divider = trim($divider, '|');
925
-
926
-			$dividerCells = explode('|', $divider);
927
-
928
-			foreach ($dividerCells as $dividerCell)
929
-			{
930
-				$dividerCell = trim($dividerCell);
931
-
932
-				if ($dividerCell === '')
933
-				{
934
-					return;
935
-				}
936
-
937
-				$alignment = null;
938
-
939
-				if ($dividerCell[0] === ':')
940
-				{
941
-					$alignment = 'left';
942
-				}
943
-
944
-				if (substr($dividerCell, - 1) === ':')
945
-				{
946
-					$alignment = $alignment === 'left' ? 'center' : 'right';
947
-				}
948
-
949
-				$alignments []= $alignment;
950
-			}
951
-
952
-			# ~
38
+        protected function textElements($text)
39
+        {
40
+            # make sure no definitions are set
41
+            $this->DefinitionData = array();
42
+
43
+            # standardize line breaks
44
+            $text = str_replace(array("\r\n", "\r"), "\n", $text);
45
+
46
+            # remove surrounding line breaks
47
+            $text = trim($text, "\n");
48
+
49
+            # split text into lines
50
+            $lines = explode("\n", $text);
51
+
52
+            # iterate through lines to identify blocks
53
+            return $this->linesElements($lines);
54
+        }
55
+
56
+        #
57
+        # Setters
58
+        #
59
+
60
+        function setBreaksEnabled($breaksEnabled)
61
+        {
62
+            $this->breaksEnabled = $breaksEnabled;
63
+
64
+            return $this;
65
+        }
66
+
67
+        protected $breaksEnabled;
68
+
69
+        function setMarkupEscaped($markupEscaped)
70
+        {
71
+            $this->markupEscaped = $markupEscaped;
72
+
73
+            return $this;
74
+        }
75
+
76
+        protected $markupEscaped;
77
+
78
+        function setUrlsLinked($urlsLinked)
79
+        {
80
+            $this->urlsLinked = $urlsLinked;
81
+
82
+            return $this;
83
+        }
84
+
85
+        protected $urlsLinked = true;
86
+
87
+        function setSafeMode($safeMode)
88
+        {
89
+            $this->safeMode = (bool) $safeMode;
90
+
91
+            return $this;
92
+        }
93
+
94
+        protected $safeMode;
95
+
96
+        function setStrictMode($strictMode)
97
+        {
98
+            $this->strictMode = (bool) $strictMode;
99
+
100
+            return $this;
101
+        }
102
+
103
+        protected $strictMode;
104
+
105
+        protected $safeLinksWhitelist = array(
106
+            'http://',
107
+            'https://',
108
+            'ftp://',
109
+            'ftps://',
110
+            'mailto:',
111
+            'tel:',
112
+            'data:image/png;base64,',
113
+            'data:image/gif;base64,',
114
+            'data:image/jpeg;base64,',
115
+            'irc:',
116
+            'ircs:',
117
+            'git:',
118
+            'ssh:',
119
+            'news:',
120
+            'steam:',
121
+        );
122
+
123
+        #
124
+        # Lines
125
+        #
126
+
127
+        protected $BlockTypes = array(
128
+            '#' => array('Header'),
129
+            '*' => array('Rule', 'List'),
130
+            '+' => array('List'),
131
+            '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
132
+            '0' => array('List'),
133
+            '1' => array('List'),
134
+            '2' => array('List'),
135
+            '3' => array('List'),
136
+            '4' => array('List'),
137
+            '5' => array('List'),
138
+            '6' => array('List'),
139
+            '7' => array('List'),
140
+            '8' => array('List'),
141
+            '9' => array('List'),
142
+            ':' => array('Table'),
143
+            '<' => array('Comment', 'Markup'),
144
+            '=' => array('SetextHeader'),
145
+            '>' => array('Quote'),
146
+            '[' => array('Reference'),
147
+            '_' => array('Rule'),
148
+            '`' => array('FencedCode'),
149
+            '|' => array('Table'),
150
+            '~' => array('FencedCode'),
151
+        );
152
+
153
+        # ~
154
+
155
+        protected $unmarkedBlockTypes = array(
156
+            'Code',
157
+        );
158
+
159
+        #
160
+        # Blocks
161
+        #
162
+
163
+        protected function lines(array $lines)
164
+        {
165
+            return $this->elements($this->linesElements($lines));
166
+        }
167
+
168
+        protected function linesElements(array $lines)
169
+        {
170
+            $Elements = array();
171
+            $CurrentBlock = null;
172
+
173
+            foreach ($lines as $line)
174
+            {
175
+                if (chop($line) === '')
176
+                {
177
+                    if (isset($CurrentBlock))
178
+                    {
179
+                        $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
180
+                            ? $CurrentBlock['interrupted'] + 1 : 1
181
+                        );
182
+                    }
183
+
184
+                    continue;
185
+                }
186
+
187
+                while (($beforeTab = strstr($line, "\t", true)) !== false)
188
+                {
189
+                    $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
190
+
191
+                    $line = $beforeTab
192
+                            . str_repeat(' ', $shortage)
193
+                            . substr($line, strlen($beforeTab) + 1)
194
+                    ;
195
+                }
196
+
197
+                $indent = strspn($line, ' ');
198
+
199
+                $text = $indent > 0 ? substr($line, $indent) : $line;
200
+
201
+                # ~
202
+
203
+                $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
204
+
205
+                # ~
206
+
207
+                if (isset($CurrentBlock['continuable']))
208
+                {
209
+                    $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
210
+                    $Block = $this->$methodName($Line, $CurrentBlock);
211
+
212
+                    if (isset($Block))
213
+                    {
214
+                        $CurrentBlock = $Block;
215
+
216
+                        continue;
217
+                    }
218
+                    else
219
+                    {
220
+                        if ($this->isBlockCompletable($CurrentBlock['type']))
221
+                        {
222
+                            $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
223
+                            $CurrentBlock = $this->$methodName($CurrentBlock);
224
+                        }
225
+                    }
226
+                }
227
+
228
+                # ~
229
+
230
+                $marker = $text[0];
231
+
232
+                # ~
233
+
234
+                $blockTypes = $this->unmarkedBlockTypes;
235
+
236
+                if (isset($this->BlockTypes[$marker]))
237
+                {
238
+                    foreach ($this->BlockTypes[$marker] as $blockType)
239
+                    {
240
+                        $blockTypes []= $blockType;
241
+                    }
242
+                }
243
+
244
+                #
245
+                # ~
246
+
247
+                foreach ($blockTypes as $blockType)
248
+                {
249
+                    $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
250
+
251
+                    if (isset($Block))
252
+                    {
253
+                        $Block['type'] = $blockType;
254
+
255
+                        if ( ! isset($Block['identified']))
256
+                        {
257
+                            if (isset($CurrentBlock))
258
+                            {
259
+                                $Elements[] = $this->extractElement($CurrentBlock);
260
+                            }
261
+
262
+                            $Block['identified'] = true;
263
+                        }
264
+
265
+                        if ($this->isBlockContinuable($blockType))
266
+                        {
267
+                            $Block['continuable'] = true;
268
+                        }
269
+
270
+                        $CurrentBlock = $Block;
271
+
272
+                        continue 2;
273
+                    }
274
+                }
275
+
276
+                # ~
277
+
278
+                if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
279
+                {
280
+                    $Block = $this->paragraphContinue($Line, $CurrentBlock);
281
+                }
282
+
283
+                if (isset($Block))
284
+                {
285
+                    $CurrentBlock = $Block;
286
+                }
287
+                else
288
+                {
289
+                    if (isset($CurrentBlock))
290
+                    {
291
+                        $Elements[] = $this->extractElement($CurrentBlock);
292
+                    }
293
+
294
+                    $CurrentBlock = $this->paragraph($Line);
295
+
296
+                    $CurrentBlock['identified'] = true;
297
+                }
298
+            }
299
+
300
+            # ~
301
+
302
+            if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
303
+            {
304
+                $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
305
+                $CurrentBlock = $this->$methodName($CurrentBlock);
306
+            }
307
+
308
+            # ~
309
+
310
+            if (isset($CurrentBlock))
311
+            {
312
+                $Elements[] = $this->extractElement($CurrentBlock);
313
+            }
314
+
315
+            # ~
316
+
317
+            return $Elements;
318
+        }
319
+
320
+        protected function extractElement(array $Component)
321
+        {
322
+            if ( ! isset($Component['element']))
323
+            {
324
+                if (isset($Component['markup']))
325
+                {
326
+                    $Component['element'] = array('rawHtml' => $Component['markup']);
327
+                }
328
+                elseif (isset($Component['hidden']))
329
+                {
330
+                    $Component['element'] = array();
331
+                }
332
+            }
333
+
334
+            return $Component['element'];
335
+        }
336
+
337
+        protected function isBlockContinuable($Type)
338
+        {
339
+            return method_exists($this, 'block' . $Type . 'Continue');
340
+        }
341
+
342
+        protected function isBlockCompletable($Type)
343
+        {
344
+            return method_exists($this, 'block' . $Type . 'Complete');
345
+        }
346
+
347
+        #
348
+        # Code
349
+
350
+        protected function blockCode($Line, $Block = null)
351
+        {
352
+            if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
353
+            {
354
+                return;
355
+            }
356
+
357
+            if ($Line['indent'] >= 4)
358
+            {
359
+                $text = substr($Line['body'], 4);
360
+
361
+                $Block = array(
362
+                    'element' => array(
363
+                        'name' => 'pre',
364
+                        'element' => array(
365
+                            'name' => 'code',
366
+                            'text' => $text,
367
+                        ),
368
+                    ),
369
+                );
370
+
371
+                return $Block;
372
+            }
373
+        }
374
+
375
+        protected function blockCodeContinue($Line, $Block)
376
+        {
377
+            if ($Line['indent'] >= 4)
378
+            {
379
+                if (isset($Block['interrupted']))
380
+                {
381
+                    $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
382
+
383
+                    unset($Block['interrupted']);
384
+                }
385
+
386
+                $Block['element']['element']['text'] .= "\n";
387
+
388
+                $text = substr($Line['body'], 4);
389
+
390
+                $Block['element']['element']['text'] .= $text;
391
+
392
+                return $Block;
393
+            }
394
+        }
395
+
396
+        protected function blockCodeComplete($Block)
397
+        {
398
+            return $Block;
399
+        }
400
+
401
+        #
402
+        # Comment
403
+
404
+        protected function blockComment($Line)
405
+        {
406
+            if ($this->markupEscaped or $this->safeMode)
407
+            {
408
+                return;
409
+            }
410
+
411
+            if (strpos($Line['text'], '<!--') === 0)
412
+            {
413
+                $Block = array(
414
+                    'element' => array(
415
+                        'rawHtml' => $Line['body'],
416
+                        'autobreak' => true,
417
+                    ),
418
+                );
419
+
420
+                if (strpos($Line['text'], '-->') !== false)
421
+                {
422
+                    $Block['closed'] = true;
423
+                }
424
+
425
+                return $Block;
426
+            }
427
+        }
428
+
429
+        protected function blockCommentContinue($Line, array $Block)
430
+        {
431
+            if (isset($Block['closed']))
432
+            {
433
+                return;
434
+            }
435
+
436
+            $Block['element']['rawHtml'] .= "\n" . $Line['body'];
437
+
438
+            if (strpos($Line['text'], '-->') !== false)
439
+            {
440
+                $Block['closed'] = true;
441
+            }
442
+
443
+            return $Block;
444
+        }
445
+
446
+        #
447
+        # Fenced Code
448
+
449
+        protected function blockFencedCode($Line)
450
+        {
451
+            $marker = $Line['text'][0];
452
+
453
+            $openerLength = strspn($Line['text'], $marker);
454
+
455
+            if ($openerLength < 3)
456
+            {
457
+                return;
458
+            }
459
+
460
+            $infostring = trim(substr($Line['text'], $openerLength), "\t ");
461
+
462
+            if (strpos($infostring, '`') !== false)
463
+            {
464
+                return;
465
+            }
466
+
467
+            $Element = array(
468
+                'name' => 'code',
469
+                'text' => '',
470
+            );
471
+
472
+            if ($infostring !== '')
473
+            {
474
+                /**
475
+                 * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
476
+                 * Every HTML element may have a class attribute specified.
477
+                 * The attribute, if specified, must have a value that is a set
478
+                 * of space-separated tokens representing the various classes
479
+                 * that the element belongs to.
480
+                 * [...]
481
+                 * The space characters, for the purposes of this specification,
482
+                 * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
483
+                 * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
484
+                 * U+000D CARRIAGE RETURN (CR).
485
+                 */
486
+                $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
487
+
488
+                $Element['attributes'] = array('class' => "language-$language");
489
+            }
490
+
491
+            $Block = array(
492
+                'char' => $marker,
493
+                'openerLength' => $openerLength,
494
+                'element' => array(
495
+                    'name' => 'pre',
496
+                    'element' => $Element,
497
+                ),
498
+            );
499
+
500
+            return $Block;
501
+        }
502
+
503
+        protected function blockFencedCodeContinue($Line, $Block)
504
+        {
505
+            if (isset($Block['complete']))
506
+            {
507
+                return;
508
+            }
509
+
510
+            if (isset($Block['interrupted']))
511
+            {
512
+                $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
513
+
514
+                unset($Block['interrupted']);
515
+            }
516
+
517
+            if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
518
+                and chop(substr($Line['text'], $len), ' ') === ''
519
+            ) {
520
+                $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
521
+
522
+                $Block['complete'] = true;
523
+
524
+                return $Block;
525
+            }
526
+
527
+            $Block['element']['element']['text'] .= "\n" . $Line['body'];
528
+
529
+            return $Block;
530
+        }
531
+
532
+        protected function blockFencedCodeComplete($Block)
533
+        {
534
+            return $Block;
535
+        }
536
+
537
+        #
538
+        # Header
539
+
540
+        protected function blockHeader($Line)
541
+        {
542
+            $level = strspn($Line['text'], '#');
543
+
544
+            if ($level > 6)
545
+            {
546
+                return;
547
+            }
548
+
549
+            $text = trim($Line['text'], '#');
550
+
551
+            if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
552
+            {
553
+                return;
554
+            }
555
+
556
+            $text = trim($text, ' ');
557
+
558
+            $Block = array(
559
+                'element' => array(
560
+                    'name' => 'h' . $level,
561
+                    'handler' => array(
562
+                        'function' => 'lineElements',
563
+                        'argument' => $text,
564
+                        'destination' => 'elements',
565
+                    )
566
+                ),
567
+            );
568
+
569
+            return $Block;
570
+        }
571
+
572
+        #
573
+        # List
574
+
575
+        protected function blockList($Line, array $CurrentBlock = null)
576
+        {
577
+            list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
578
+
579
+            if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
580
+            {
581
+                $contentIndent = strlen($matches[2]);
582
+
583
+                if ($contentIndent >= 5)
584
+                {
585
+                    $contentIndent -= 1;
586
+                    $matches[1] = substr($matches[1], 0, -$contentIndent);
587
+                    $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
588
+                }
589
+                elseif ($contentIndent === 0)
590
+                {
591
+                    $matches[1] .= ' ';
592
+                }
593
+
594
+                $markerWithoutWhitespace = strstr($matches[1], ' ', true);
595
+
596
+                $Block = array(
597
+                    'indent' => $Line['indent'],
598
+                    'pattern' => $pattern,
599
+                    'data' => array(
600
+                        'type' => $name,
601
+                        'marker' => $matches[1],
602
+                        'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
603
+                    ),
604
+                    'element' => array(
605
+                        'name' => $name,
606
+                        'elements' => array(),
607
+                    ),
608
+                );
609
+                $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
610
+
611
+                if ($name === 'ol')
612
+                {
613
+                    $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
614
+
615
+                    if ($listStart !== '1')
616
+                    {
617
+                        if (
618
+                            isset($CurrentBlock)
619
+                            and $CurrentBlock['type'] === 'Paragraph'
620
+                                and ! isset($CurrentBlock['interrupted'])
621
+                        ) {
622
+                            return;
623
+                        }
624
+
625
+                        $Block['element']['attributes'] = array('start' => $listStart);
626
+                    }
627
+                }
628
+
629
+                $Block['li'] = array(
630
+                    'name' => 'li',
631
+                    'handler' => array(
632
+                        'function' => 'li',
633
+                        'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
634
+                        'destination' => 'elements'
635
+                    )
636
+                );
637
+
638
+                $Block['element']['elements'] []= & $Block['li'];
639
+
640
+                return $Block;
641
+            }
642
+        }
643
+
644
+        protected function blockListContinue($Line, array $Block)
645
+        {
646
+            if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
647
+            {
648
+                return null;
649
+            }
650
+
651
+            $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
652
+
653
+            if ($Line['indent'] < $requiredIndent
654
+                and (
655
+                    (
656
+                        $Block['data']['type'] === 'ol'
657
+                        and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
658
+                    ) or (
659
+                        $Block['data']['type'] === 'ul'
660
+                        and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
661
+                    )
662
+                )
663
+            ) {
664
+                if (isset($Block['interrupted']))
665
+                {
666
+                    $Block['li']['handler']['argument'] []= '';
667
+
668
+                    $Block['loose'] = true;
669
+
670
+                    unset($Block['interrupted']);
671
+                }
672
+
673
+                unset($Block['li']);
674
+
675
+                $text = isset($matches[1]) ? $matches[1] : '';
676
+
677
+                $Block['indent'] = $Line['indent'];
678
+
679
+                $Block['li'] = array(
680
+                    'name' => 'li',
681
+                    'handler' => array(
682
+                        'function' => 'li',
683
+                        'argument' => array($text),
684
+                        'destination' => 'elements'
685
+                    )
686
+                );
687
+
688
+                $Block['element']['elements'] []= & $Block['li'];
689
+
690
+                return $Block;
691
+            }
692
+            elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
693
+            {
694
+                return null;
695
+            }
696
+
697
+            if ($Line['text'][0] === '[' and $this->blockReference($Line))
698
+            {
699
+                return $Block;
700
+            }
701
+
702
+            if ($Line['indent'] >= $requiredIndent)
703
+            {
704
+                if (isset($Block['interrupted']))
705
+                {
706
+                    $Block['li']['handler']['argument'] []= '';
707
+
708
+                    $Block['loose'] = true;
709
+
710
+                    unset($Block['interrupted']);
711
+                }
712
+
713
+                $text = substr($Line['body'], $requiredIndent);
714
+
715
+                $Block['li']['handler']['argument'] []= $text;
716
+
717
+                return $Block;
718
+            }
719
+
720
+            if ( ! isset($Block['interrupted']))
721
+            {
722
+                $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
723
+
724
+                $Block['li']['handler']['argument'] []= $text;
725
+
726
+                return $Block;
727
+            }
728
+        }
729
+
730
+        protected function blockListComplete(array $Block)
731
+        {
732
+            if (isset($Block['loose']))
733
+            {
734
+                foreach ($Block['element']['elements'] as &$li)
735
+                {
736
+                    if (end($li['handler']['argument']) !== '')
737
+                    {
738
+                        $li['handler']['argument'] []= '';
739
+                    }
740
+                }
741
+            }
742
+
743
+            return $Block;
744
+        }
745
+
746
+        #
747
+        # Quote
748
+
749
+        protected function blockQuote($Line)
750
+        {
751
+            if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
752
+            {
753
+                $Block = array(
754
+                    'element' => array(
755
+                        'name' => 'blockquote',
756
+                        'handler' => array(
757
+                            'function' => 'linesElements',
758
+                            'argument' => (array) $matches[1],
759
+                            'destination' => 'elements',
760
+                        )
761
+                    ),
762
+                );
763
+
764
+                return $Block;
765
+            }
766
+        }
767
+
768
+        protected function blockQuoteContinue($Line, array $Block)
769
+        {
770
+            if (isset($Block['interrupted']))
771
+            {
772
+                return;
773
+            }
774
+
775
+            if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
776
+            {
777
+                $Block['element']['handler']['argument'] []= $matches[1];
778
+
779
+                return $Block;
780
+            }
781
+
782
+            if ( ! isset($Block['interrupted']))
783
+            {
784
+                $Block['element']['handler']['argument'] []= $Line['text'];
785
+
786
+                return $Block;
787
+            }
788
+        }
789
+
790
+        #
791
+        # Rule
792
+
793
+        protected function blockRule($Line)
794
+        {
795
+            $marker = $Line['text'][0];
796
+
797
+            if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
798
+            {
799
+                $Block = array(
800
+                    'element' => array(
801
+                        'name' => 'hr',
802
+                    ),
803
+                );
804
+
805
+                return $Block;
806
+            }
807
+        }
808
+
809
+        #
810
+        # Setext
811
+
812
+        protected function blockSetextHeader($Line, array $Block = null)
813
+        {
814
+            if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
815
+            {
816
+                return;
817
+            }
818
+
819
+            if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
820
+            {
821
+                $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
822
+
823
+                return $Block;
824
+            }
825
+        }
826
+
827
+        #
828
+        # Markup
829
+
830
+        protected function blockMarkup($Line)
831
+        {
832
+            if ($this->markupEscaped or $this->safeMode)
833
+            {
834
+                return;
835
+            }
836
+
837
+            if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
838
+            {
839
+                $element = strtolower($matches[1]);
840
+
841
+                if (in_array($element, $this->textLevelElements))
842
+                {
843
+                    return;
844
+                }
845
+
846
+                $Block = array(
847
+                    'name' => $matches[1],
848
+                    'element' => array(
849
+                        'rawHtml' => $Line['text'],
850
+                        'autobreak' => true,
851
+                    ),
852
+                );
853
+
854
+                return $Block;
855
+            }
856
+        }
857
+
858
+        protected function blockMarkupContinue($Line, array $Block)
859
+        {
860
+            if (isset($Block['closed']) or isset($Block['interrupted']))
861
+            {
862
+                return;
863
+            }
864
+
865
+            $Block['element']['rawHtml'] .= "\n" . $Line['body'];
866
+
867
+            return $Block;
868
+        }
869
+
870
+        #
871
+        # Reference
872
+
873
+        protected function blockReference($Line)
874
+        {
875
+            if (strpos($Line['text'], ']') !== false
876
+                and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
877
+            ) {
878
+                $id = strtolower($matches[1]);
879
+
880
+                $Data = array(
881
+                    'url' => $matches[2],
882
+                    'title' => isset($matches[3]) ? $matches[3] : null,
883
+                );
884
+
885
+                $this->DefinitionData['Reference'][$id] = $Data;
886
+
887
+                $Block = array(
888
+                    'element' => array(),
889
+                );
890
+
891
+                return $Block;
892
+            }
893
+        }
894
+
895
+        #
896
+        # Table
897
+
898
+        protected function blockTable($Line, array $Block = null)
899
+        {
900
+            if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
901
+            {
902
+                return;
903
+            }
904
+
905
+            if (
906
+                strpos($Block['element']['handler']['argument'], '|') === false
907
+                and strpos($Line['text'], '|') === false
908
+                    and strpos($Line['text'], ':') === false
909
+                or strpos($Block['element']['handler']['argument'], "\n") !== false
910
+            ) {
911
+                return;
912
+            }
913
+
914
+            if (chop($Line['text'], ' -:|') !== '')
915
+            {
916
+                return;
917
+            }
918
+
919
+            $alignments = array();
920
+
921
+            $divider = $Line['text'];
922
+
923
+            $divider = trim($divider);
924
+            $divider = trim($divider, '|');
925
+
926
+            $dividerCells = explode('|', $divider);
927
+
928
+            foreach ($dividerCells as $dividerCell)
929
+            {
930
+                $dividerCell = trim($dividerCell);
931
+
932
+                if ($dividerCell === '')
933
+                {
934
+                    return;
935
+                }
936
+
937
+                $alignment = null;
938
+
939
+                if ($dividerCell[0] === ':')
940
+                {
941
+                    $alignment = 'left';
942
+                }
943
+
944
+                if (substr($dividerCell, - 1) === ':')
945
+                {
946
+                    $alignment = $alignment === 'left' ? 'center' : 'right';
947
+                }
948
+
949
+                $alignments []= $alignment;
950
+            }
951
+
952
+            # ~
953 953
 
954
-			$HeaderElements = array();
955
-
956
-			$header = $Block['element']['handler']['argument'];
957
-
958
-			$header = trim($header);
959
-			$header = trim($header, '|');
960
-
961
-			$headerCells = explode('|', $header);
962
-
963
-			if (count($headerCells) !== count($alignments))
964
-			{
965
-				return;
966
-			}
967
-
968
-			foreach ($headerCells as $index => $headerCell)
969
-			{
970
-				$headerCell = trim($headerCell);
971
-
972
-				$HeaderElement = array(
973
-					'name' => 'th',
974
-					'handler' => array(
975
-						'function' => 'lineElements',
976
-						'argument' => $headerCell,
977
-						'destination' => 'elements',
978
-					)
979
-				);
980
-
981
-				if (isset($alignments[$index]))
982
-				{
983
-					$alignment = $alignments[$index];
984
-
985
-					$HeaderElement['attributes'] = array(
986
-						'style' => "text-align: $alignment;",
987
-					);
988
-				}
989
-
990
-				$HeaderElements []= $HeaderElement;
991
-			}
992
-
993
-			# ~
994
-
995
-			$Block = array(
996
-				'alignments' => $alignments,
997
-				'identified' => true,
998
-				'element' => array(
999
-					'name' => 'table',
1000
-					'elements' => array(),
1001
-				),
1002
-			);
1003
-
1004
-			$Block['element']['elements'] []= array(
1005
-				'name' => 'thead',
1006
-			);
1007
-
1008
-			$Block['element']['elements'] []= array(
1009
-				'name' => 'tbody',
1010
-				'elements' => array(),
1011
-			);
1012
-
1013
-			$Block['element']['elements'][0]['elements'] []= array(
1014
-				'name' => 'tr',
1015
-				'elements' => $HeaderElements,
1016
-			);
1017
-
1018
-			return $Block;
1019
-		}
1020
-
1021
-		protected function blockTableContinue($Line, array $Block)
1022
-		{
1023
-			if (isset($Block['interrupted']))
1024
-			{
1025
-				return;
1026
-			}
1027
-
1028
-			if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
1029
-			{
1030
-				$Elements = array();
1031
-
1032
-				$row = $Line['text'];
1033
-
1034
-				$row = trim($row);
1035
-				$row = trim($row, '|');
1036
-
1037
-				preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
1038
-
1039
-				$cells = array_slice($matches[0], 0, count($Block['alignments']));
1040
-
1041
-				foreach ($cells as $index => $cell)
1042
-				{
1043
-					$cell = trim($cell);
1044
-
1045
-					$Element = array(
1046
-						'name' => 'td',
1047
-						'handler' => array(
1048
-							'function' => 'lineElements',
1049
-							'argument' => $cell,
1050
-							'destination' => 'elements',
1051
-						)
1052
-					);
1053
-
1054
-					if (isset($Block['alignments'][$index]))
1055
-					{
1056
-						$Element['attributes'] = array(
1057
-							'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
1058
-						);
1059
-					}
1060
-
1061
-					$Elements []= $Element;
1062
-				}
1063
-
1064
-				$Element = array(
1065
-					'name' => 'tr',
1066
-					'elements' => $Elements,
1067
-				);
1068
-
1069
-				$Block['element']['elements'][1]['elements'] []= $Element;
1070
-
1071
-				return $Block;
1072
-			}
1073
-		}
1074
-
1075
-		#
1076
-		# ~
1077
-		#
1078
-
1079
-		protected function paragraph($Line)
1080
-		{
1081
-			return array(
1082
-				'type' => 'Paragraph',
1083
-				'element' => array(
1084
-					'name' => 'p',
1085
-					'handler' => array(
1086
-						'function' => 'lineElements',
1087
-						'argument' => $Line['text'],
1088
-						'destination' => 'elements',
1089
-					),
1090
-				),
1091
-			);
1092
-		}
1093
-
1094
-		protected function paragraphContinue($Line, array $Block)
1095
-		{
1096
-			if (isset($Block['interrupted']))
1097
-			{
1098
-				return;
1099
-			}
1100
-
1101
-			$Block['element']['handler']['argument'] .= "\n".$Line['text'];
1102
-
1103
-			return $Block;
1104
-		}
1105
-
1106
-		#
1107
-		# Inline Elements
1108
-		#
1109
-
1110
-		protected $InlineTypes = array(
1111
-			'!' => array('Image'),
1112
-			'&' => array('SpecialCharacter'),
1113
-			'*' => array('Emphasis'),
1114
-			':' => array('Url'),
1115
-			'<' => array('UrlTag', 'EmailTag', 'Markup'),
1116
-			'[' => array('Link'),
1117
-			'_' => array('Emphasis'),
1118
-			'`' => array('Code'),
1119
-			'~' => array('Strikethrough'),
1120
-			'\\' => array('EscapeSequence'),
1121
-		);
954
+            $HeaderElements = array();
955
+
956
+            $header = $Block['element']['handler']['argument'];
957
+
958
+            $header = trim($header);
959
+            $header = trim($header, '|');
960
+
961
+            $headerCells = explode('|', $header);
962
+
963
+            if (count($headerCells) !== count($alignments))
964
+            {
965
+                return;
966
+            }
967
+
968
+            foreach ($headerCells as $index => $headerCell)
969
+            {
970
+                $headerCell = trim($headerCell);
971
+
972
+                $HeaderElement = array(
973
+                    'name' => 'th',
974
+                    'handler' => array(
975
+                        'function' => 'lineElements',
976
+                        'argument' => $headerCell,
977
+                        'destination' => 'elements',
978
+                    )
979
+                );
980
+
981
+                if (isset($alignments[$index]))
982
+                {
983
+                    $alignment = $alignments[$index];
984
+
985
+                    $HeaderElement['attributes'] = array(
986
+                        'style' => "text-align: $alignment;",
987
+                    );
988
+                }
989
+
990
+                $HeaderElements []= $HeaderElement;
991
+            }
992
+
993
+            # ~
994
+
995
+            $Block = array(
996
+                'alignments' => $alignments,
997
+                'identified' => true,
998
+                'element' => array(
999
+                    'name' => 'table',
1000
+                    'elements' => array(),
1001
+                ),
1002
+            );
1003
+
1004
+            $Block['element']['elements'] []= array(
1005
+                'name' => 'thead',
1006
+            );
1007
+
1008
+            $Block['element']['elements'] []= array(
1009
+                'name' => 'tbody',
1010
+                'elements' => array(),
1011
+            );
1012
+
1013
+            $Block['element']['elements'][0]['elements'] []= array(
1014
+                'name' => 'tr',
1015
+                'elements' => $HeaderElements,
1016
+            );
1017
+
1018
+            return $Block;
1019
+        }
1020
+
1021
+        protected function blockTableContinue($Line, array $Block)
1022
+        {
1023
+            if (isset($Block['interrupted']))
1024
+            {
1025
+                return;
1026
+            }
1027
+
1028
+            if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
1029
+            {
1030
+                $Elements = array();
1031
+
1032
+                $row = $Line['text'];
1033
+
1034
+                $row = trim($row);
1035
+                $row = trim($row, '|');
1036
+
1037
+                preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
1038
+
1039
+                $cells = array_slice($matches[0], 0, count($Block['alignments']));
1040
+
1041
+                foreach ($cells as $index => $cell)
1042
+                {
1043
+                    $cell = trim($cell);
1044
+
1045
+                    $Element = array(
1046
+                        'name' => 'td',
1047
+                        'handler' => array(
1048
+                            'function' => 'lineElements',
1049
+                            'argument' => $cell,
1050
+                            'destination' => 'elements',
1051
+                        )
1052
+                    );
1053
+
1054
+                    if (isset($Block['alignments'][$index]))
1055
+                    {
1056
+                        $Element['attributes'] = array(
1057
+                            'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
1058
+                        );
1059
+                    }
1060
+
1061
+                    $Elements []= $Element;
1062
+                }
1063
+
1064
+                $Element = array(
1065
+                    'name' => 'tr',
1066
+                    'elements' => $Elements,
1067
+                );
1068
+
1069
+                $Block['element']['elements'][1]['elements'] []= $Element;
1070
+
1071
+                return $Block;
1072
+            }
1073
+        }
1074
+
1075
+        #
1076
+        # ~
1077
+        #
1078
+
1079
+        protected function paragraph($Line)
1080
+        {
1081
+            return array(
1082
+                'type' => 'Paragraph',
1083
+                'element' => array(
1084
+                    'name' => 'p',
1085
+                    'handler' => array(
1086
+                        'function' => 'lineElements',
1087
+                        'argument' => $Line['text'],
1088
+                        'destination' => 'elements',
1089
+                    ),
1090
+                ),
1091
+            );
1092
+        }
1093
+
1094
+        protected function paragraphContinue($Line, array $Block)
1095
+        {
1096
+            if (isset($Block['interrupted']))
1097
+            {
1098
+                return;
1099
+            }
1100
+
1101
+            $Block['element']['handler']['argument'] .= "\n".$Line['text'];
1102
+
1103
+            return $Block;
1104
+        }
1105
+
1106
+        #
1107
+        # Inline Elements
1108
+        #
1109
+
1110
+        protected $InlineTypes = array(
1111
+            '!' => array('Image'),
1112
+            '&' => array('SpecialCharacter'),
1113
+            '*' => array('Emphasis'),
1114
+            ':' => array('Url'),
1115
+            '<' => array('UrlTag', 'EmailTag', 'Markup'),
1116
+            '[' => array('Link'),
1117
+            '_' => array('Emphasis'),
1118
+            '`' => array('Code'),
1119
+            '~' => array('Strikethrough'),
1120
+            '\\' => array('EscapeSequence'),
1121
+        );
1122 1122
 
1123
-		# ~
1123
+        # ~
1124 1124
 
1125
-		protected $inlineMarkerList = '!*_&[:<`~\\';
1125
+        protected $inlineMarkerList = '!*_&[:<`~\\';
1126 1126
 
1127
-		#
1128
-		# ~
1129
-		#
1127
+        #
1128
+        # ~
1129
+        #
1130 1130
 
1131
-		public function line($text, $nonNestables = array())
1132
-		{
1133
-			return $this->elements($this->lineElements($text, $nonNestables));
1134
-		}
1131
+        public function line($text, $nonNestables = array())
1132
+        {
1133
+            return $this->elements($this->lineElements($text, $nonNestables));
1134
+        }
1135 1135
 
1136
-		protected function lineElements($text, $nonNestables = array())
1137
-		{
1138
-			# standardize line breaks
1139
-			$text = str_replace(array("\r\n", "\r"), "\n", $text);
1136
+        protected function lineElements($text, $nonNestables = array())
1137
+        {
1138
+            # standardize line breaks
1139
+            $text = str_replace(array("\r\n", "\r"), "\n", $text);
1140 1140
 
1141
-			$Elements = array();
1142
-
1143
-			$nonNestables = (empty($nonNestables)
1144
-				? array()
1145
-				: array_combine($nonNestables, $nonNestables)
1146
-			);
1147
-
1148
-			# $excerpt is based on the first occurrence of a marker
1149
-
1150
-			while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1151
-			{
1152
-				$marker = $excerpt[0];
1153
-
1154
-				$markerPosition = strlen($text) - strlen($excerpt);
1155
-
1156
-				$Excerpt = array('text' => $excerpt, 'context' => $text);
1157
-
1158
-				foreach ($this->InlineTypes[$marker] as $inlineType)
1159
-				{
1160
-					# check to see if the current inline type is nestable in the current context
1161
-
1162
-					if (isset($nonNestables[$inlineType]))
1163
-					{
1164
-						continue;
1165
-					}
1166
-
1167
-					$Inline = $this->{"inline$inlineType"}($Excerpt);
1141
+            $Elements = array();
1142
+
1143
+            $nonNestables = (empty($nonNestables)
1144
+                ? array()
1145
+                : array_combine($nonNestables, $nonNestables)
1146
+            );
1147
+
1148
+            # $excerpt is based on the first occurrence of a marker
1149
+
1150
+            while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1151
+            {
1152
+                $marker = $excerpt[0];
1153
+
1154
+                $markerPosition = strlen($text) - strlen($excerpt);
1155
+
1156
+                $Excerpt = array('text' => $excerpt, 'context' => $text);
1157
+
1158
+                foreach ($this->InlineTypes[$marker] as $inlineType)
1159
+                {
1160
+                    # check to see if the current inline type is nestable in the current context
1161
+
1162
+                    if (isset($nonNestables[$inlineType]))
1163
+                    {
1164
+                        continue;
1165
+                    }
1166
+
1167
+                    $Inline = $this->{"inline$inlineType"}($Excerpt);
1168 1168
 
1169
-					if ( ! isset($Inline))
1170
-					{
1171
-						continue;
1172
-					}
1173
-
1174
-					# makes sure that the inline belongs to "our" marker
1175
-
1176
-					if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1177
-					{
1178
-						continue;
1179
-					}
1180
-
1181
-					# sets a default inline position
1182
-
1183
-					if ( ! isset($Inline['position']))
1184
-					{
1185
-						$Inline['position'] = $markerPosition;
1186
-					}
1187
-
1188
-					# cause the new element to 'inherit' our non nestables
1169
+                    if ( ! isset($Inline))
1170
+                    {
1171
+                        continue;
1172
+                    }
1173
+
1174
+                    # makes sure that the inline belongs to "our" marker
1175
+
1176
+                    if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1177
+                    {
1178
+                        continue;
1179
+                    }
1180
+
1181
+                    # sets a default inline position
1182
+
1183
+                    if ( ! isset($Inline['position']))
1184
+                    {
1185
+                        $Inline['position'] = $markerPosition;
1186
+                    }
1187
+
1188
+                    # cause the new element to 'inherit' our non nestables
1189 1189
 
1190 1190
 
1191
-					$Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
1192
-						? array_merge($Inline['element']['nonNestables'], $nonNestables)
1193
-						: $nonNestables
1194
-					;
1191
+                    $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
1192
+                        ? array_merge($Inline['element']['nonNestables'], $nonNestables)
1193
+                        : $nonNestables
1194
+                    ;
1195 1195
 
1196
-					# the text that comes before the inline
1197
-					$unmarkedText = substr($text, 0, $Inline['position']);
1196
+                    # the text that comes before the inline
1197
+                    $unmarkedText = substr($text, 0, $Inline['position']);
1198 1198
 
1199
-					# compile the unmarked text
1200
-					$InlineText = $this->inlineText($unmarkedText);
1201
-					$Elements[] = $InlineText['element'];
1202
-
1203
-					# compile the inline
1204
-					$Elements[] = $this->extractElement($Inline);
1205
-
1206
-					# remove the examined text
1207
-					$text = substr($text, $Inline['position'] + $Inline['extent']);
1208
-
1209
-					continue 2;
1210
-				}
1211
-
1212
-				# the marker does not belong to an inline
1213
-
1214
-				$unmarkedText = substr($text, 0, $markerPosition + 1);
1215
-
1216
-				$InlineText = $this->inlineText($unmarkedText);
1217
-				$Elements[] = $InlineText['element'];
1218
-
1219
-				$text = substr($text, $markerPosition + 1);
1220
-			}
1221
-
1222
-			$InlineText = $this->inlineText($text);
1223
-			$Elements[] = $InlineText['element'];
1224
-
1225
-			foreach ($Elements as &$Element)
1226
-			{
1227
-				if ( ! isset($Element['autobreak']))
1228
-				{
1229
-					$Element['autobreak'] = false;
1230
-				}
1231
-			}
1232
-
1233
-			return $Elements;
1234
-		}
1235
-
1236
-		#
1237
-		# ~
1238
-		#
1239
-
1240
-		protected function inlineText($text)
1241
-		{
1242
-			$Inline = array(
1243
-				'extent' => strlen($text),
1244
-				'element' => array(),
1245
-			);
1246
-
1247
-			$Inline['element']['elements'] = self::pregReplaceElements(
1248
-				$this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
1249
-				array(
1250
-					array('name' => 'br'),
1251
-					array('text' => "\n"),
1252
-				),
1253
-				$text
1254
-			);
1255
-
1256
-			return $Inline;
1257
-		}
1258
-
1259
-		protected function inlineCode($Excerpt)
1260
-		{
1261
-			$marker = $Excerpt['text'][0];
1262
-
1263
-			if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1264
-			{
1265
-				$text = $matches[2];
1266
-				$text = preg_replace('/[ ]*+\n/', ' ', $text);
1267
-
1268
-				return array(
1269
-					'extent' => strlen($matches[0]),
1270
-					'element' => array(
1271
-						'name' => 'code',
1272
-						'text' => $text,
1273
-					),
1274
-				);
1275
-			}
1276
-		}
1277
-
1278
-		protected function inlineEmailTag($Excerpt)
1279
-		{
1280
-			$hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
1281
-
1282
-			$commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
1283
-			                   . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
1284
-
1285
-			if (strpos($Excerpt['text'], '>') !== false
1286
-			    and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
1287
-			){
1288
-				$url = $matches[1];
1289
-
1290
-				if ( ! isset($matches[2]))
1291
-				{
1292
-					$url = "mailto:$url";
1293
-				}
1294
-
1295
-				return array(
1296
-					'extent' => strlen($matches[0]),
1297
-					'element' => array(
1298
-						'name' => 'a',
1299
-						'text' => $matches[1],
1300
-						'attributes' => array(
1301
-							'href' => $url,
1302
-						),
1303
-					),
1304
-				);
1305
-			}
1306
-		}
1307
-
1308
-		protected function inlineEmphasis($Excerpt)
1309
-		{
1310
-			if ( ! isset($Excerpt['text'][1]))
1311
-			{
1312
-				return;
1313
-			}
1314
-
1315
-			$marker = $Excerpt['text'][0];
1316
-
1317
-			if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1318
-			{
1319
-				$emphasis = 'strong';
1320
-			}
1321
-			elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1322
-			{
1323
-				$emphasis = 'em';
1324
-			}
1325
-			else
1326
-			{
1327
-				return;
1328
-			}
1329
-
1330
-			return array(
1331
-				'extent' => strlen($matches[0]),
1332
-				'element' => array(
1333
-					'name' => $emphasis,
1334
-					'handler' => array(
1335
-						'function' => 'lineElements',
1336
-						'argument' => $matches[1],
1337
-						'destination' => 'elements',
1338
-					)
1339
-				),
1340
-			);
1341
-		}
1342
-
1343
-		protected function inlineEscapeSequence($Excerpt)
1344
-		{
1345
-			if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1346
-			{
1347
-				return array(
1348
-					'element' => array('rawHtml' => $Excerpt['text'][1]),
1349
-					'extent' => 2,
1350
-				);
1351
-			}
1352
-		}
1353
-
1354
-		protected function inlineImage($Excerpt)
1355
-		{
1356
-			if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1357
-			{
1358
-				return;
1359
-			}
1360
-
1361
-			$Excerpt['text']= substr($Excerpt['text'], 1);
1362
-
1363
-			$Link = $this->inlineLink($Excerpt);
1364
-
1365
-			if ($Link === null)
1366
-			{
1367
-				return;
1368
-			}
1369
-
1370
-			$Inline = array(
1371
-				'extent' => $Link['extent'] + 1,
1372
-				'element' => array(
1373
-					'name' => 'img',
1374
-					'attributes' => array(
1375
-						'src' => $Link['element']['attributes']['href'],
1376
-						'alt' => $Link['element']['handler']['argument'],
1377
-					),
1378
-					'autobreak' => true,
1379
-				),
1380
-			);
1381
-
1382
-			$Inline['element']['attributes'] += $Link['element']['attributes'];
1383
-
1384
-			unset($Inline['element']['attributes']['href']);
1385
-
1386
-			return $Inline;
1387
-		}
1388
-
1389
-		protected function inlineLink($Excerpt)
1390
-		{
1391
-			$Element = array(
1392
-				'name' => 'a',
1393
-				'handler' => array(
1394
-					'function' => 'lineElements',
1395
-					'argument' => null,
1396
-					'destination' => 'elements',
1397
-				),
1398
-				'nonNestables' => array('Url', 'Link'),
1399
-				'attributes' => array(
1400
-					'href' => null,
1401
-					'title' => null,
1402
-				),
1403
-			);
1404
-
1405
-			$extent = 0;
1406
-
1407
-			$remainder = $Excerpt['text'];
1408
-
1409
-			if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1410
-			{
1411
-				$Element['handler']['argument'] = $matches[1];
1412
-
1413
-				$extent += strlen($matches[0]);
1414
-
1415
-				$remainder = substr($remainder, $extent);
1416
-			}
1417
-			else
1418
-			{
1419
-				return;
1420
-			}
1421
-
1422
-			if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
1423
-			{
1424
-				$Element['attributes']['href'] = $matches[1];
1425
-
1426
-				if (isset($matches[2]))
1427
-				{
1428
-					$Element['attributes']['title'] = substr($matches[2], 1, - 1);
1429
-				}
1430
-
1431
-				$extent += strlen($matches[0]);
1432
-			}
1433
-			else
1434
-			{
1435
-				if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1436
-				{
1437
-					$definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
1438
-					$definition = strtolower($definition);
1439
-
1440
-					$extent += strlen($matches[0]);
1441
-				}
1442
-				else
1443
-				{
1444
-					$definition = strtolower($Element['handler']['argument']);
1445
-				}
1446
-
1447
-				if ( ! isset($this->DefinitionData['Reference'][$definition]))
1448
-				{
1449
-					return;
1450
-				}
1451
-
1452
-				$Definition = $this->DefinitionData['Reference'][$definition];
1453
-
1454
-				$Element['attributes']['href'] = $Definition['url'];
1455
-				$Element['attributes']['title'] = $Definition['title'];
1456
-			}
1457
-
1458
-			return array(
1459
-				'extent' => $extent,
1460
-				'element' => $Element,
1461
-			);
1462
-		}
1463
-
1464
-		protected function inlineMarkup($Excerpt)
1465
-		{
1466
-			if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
1467
-			{
1468
-				return;
1469
-			}
1470
-
1471
-			if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
1472
-			{
1473
-				return array(
1474
-					'element' => array('rawHtml' => $matches[0]),
1475
-					'extent' => strlen($matches[0]),
1476
-				);
1477
-			}
1478
-
1479
-			if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
1480
-			{
1481
-				return array(
1482
-					'element' => array('rawHtml' => $matches[0]),
1483
-					'extent' => strlen($matches[0]),
1484
-				);
1485
-			}
1486
-
1487
-			if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
1488
-			{
1489
-				return array(
1490
-					'element' => array('rawHtml' => $matches[0]),
1491
-					'extent' => strlen($matches[0]),
1492
-				);
1493
-			}
1494
-		}
1495
-
1496
-		protected function inlineSpecialCharacter($Excerpt)
1497
-		{
1498
-			if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
1499
-			                                               and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
1500
-			) {
1501
-				return array(
1502
-					'element' => array('rawHtml' => '&' . $matches[1] . ';'),
1503
-					'extent' => strlen($matches[0]),
1504
-				);
1505
-			}
1506
-
1507
-			return;
1508
-		}
1509
-
1510
-		protected function inlineStrikethrough($Excerpt)
1511
-		{
1512
-			if ( ! isset($Excerpt['text'][1]))
1513
-			{
1514
-				return;
1515
-			}
1516
-
1517
-			if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1518
-			{
1519
-				return array(
1520
-					'extent' => strlen($matches[0]),
1521
-					'element' => array(
1522
-						'name' => 'del',
1523
-						'handler' => array(
1524
-							'function' => 'lineElements',
1525
-							'argument' => $matches[1],
1526
-							'destination' => 'elements',
1527
-						)
1528
-					),
1529
-				);
1530
-			}
1531
-		}
1532
-
1533
-		protected function inlineUrl($Excerpt)
1534
-		{
1535
-			if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1536
-			{
1537
-				return;
1538
-			}
1539
-
1540
-			if (strpos($Excerpt['context'], 'http') !== false
1541
-			    and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
1542
-			) {
1543
-				$url = $matches[0][0];
1544
-
1545
-				$Inline = array(
1546
-					'extent' => strlen($matches[0][0]),
1547
-					'position' => $matches[0][1],
1548
-					'element' => array(
1549
-						'name' => 'a',
1550
-						'text' => $url,
1551
-						'attributes' => array(
1552
-							'href' => $url,
1553
-						),
1554
-					),
1555
-				);
1556
-
1557
-				return $Inline;
1558
-			}
1559
-		}
1560
-
1561
-		protected function inlineUrlTag($Excerpt)
1562
-		{
1563
-			if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
1564
-			{
1565
-				$url = $matches[1];
1566
-
1567
-				return array(
1568
-					'extent' => strlen($matches[0]),
1569
-					'element' => array(
1570
-						'name' => 'a',
1571
-						'text' => $url,
1572
-						'attributes' => array(
1573
-							'href' => $url,
1574
-						),
1575
-					),
1576
-				);
1577
-			}
1578
-		}
1579
-
1580
-		# ~
1581
-
1582
-		protected function unmarkedText($text)
1583
-		{
1584
-			$Inline = $this->inlineText($text);
1585
-			return $this->element($Inline['element']);
1586
-		}
1587
-
1588
-		#
1589
-		# Handlers
1590
-		#
1591
-
1592
-		protected function handle(array $Element)
1593
-		{
1594
-			if (isset($Element['handler']))
1595
-			{
1596
-				if (!isset($Element['nonNestables']))
1597
-				{
1598
-					$Element['nonNestables'] = array();
1599
-				}
1600
-
1601
-				if (is_string($Element['handler']))
1602
-				{
1603
-					$function = $Element['handler'];
1604
-					$argument = $Element['text'];
1605
-					unset($Element['text']);
1606
-					$destination = 'rawHtml';
1607
-				}
1608
-				else
1609
-				{
1610
-					$function = $Element['handler']['function'];
1611
-					$argument = $Element['handler']['argument'];
1612
-					$destination = $Element['handler']['destination'];
1613
-				}
1614
-
1615
-				$Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
1616
-
1617
-				if ($destination === 'handler')
1618
-				{
1619
-					$Element = $this->handle($Element);
1620
-				}
1621
-
1622
-				unset($Element['handler']);
1623
-			}
1624
-
1625
-			return $Element;
1626
-		}
1627
-
1628
-		protected function handleElementRecursive(array $Element)
1629
-		{
1630
-			return $this->elementApplyRecursive(array($this, 'handle'), $Element);
1631
-		}
1632
-
1633
-		protected function handleElementsRecursive(array $Elements)
1634
-		{
1635
-			return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
1636
-		}
1637
-
1638
-		protected function elementApplyRecursive($closure, array $Element)
1639
-		{
1640
-			$Element = call_user_func($closure, $Element);
1641
-
1642
-			if (isset($Element['elements']))
1643
-			{
1644
-				$Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
1645
-			}
1646
-			elseif (isset($Element['element']))
1647
-			{
1648
-				$Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
1649
-			}
1650
-
1651
-			return $Element;
1652
-		}
1653
-
1654
-		protected function elementApplyRecursiveDepthFirst($closure, array $Element)
1655
-		{
1656
-			if (isset($Element['elements']))
1657
-			{
1658
-				$Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
1659
-			}
1660
-			elseif (isset($Element['element']))
1661
-			{
1662
-				$Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
1663
-			}
1664
-
1665
-			$Element = call_user_func($closure, $Element);
1666
-
1667
-			return $Element;
1668
-		}
1669
-
1670
-		protected function elementsApplyRecursive($closure, array $Elements)
1671
-		{
1672
-			foreach ($Elements as &$Element)
1673
-			{
1674
-				$Element = $this->elementApplyRecursive($closure, $Element);
1675
-			}
1676
-
1677
-			return $Elements;
1678
-		}
1679
-
1680
-		protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
1681
-		{
1682
-			foreach ($Elements as &$Element)
1683
-			{
1684
-				$Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
1685
-			}
1686
-
1687
-			return $Elements;
1688
-		}
1689
-
1690
-		protected function element(array $Element)
1691
-		{
1692
-			if ($this->safeMode)
1693
-			{
1694
-				$Element = $this->sanitiseElement($Element);
1695
-			}
1696
-
1697
-			# identity map if element has no handler
1698
-			$Element = $this->handle($Element);
1699
-
1700
-			$hasName = isset($Element['name']);
1701
-
1702
-			$markup = '';
1703
-
1704
-			if ($hasName)
1705
-			{
1706
-				$markup .= '<' . $Element['name'];
1707
-
1708
-				if (isset($Element['attributes']))
1709
-				{
1710
-					foreach ($Element['attributes'] as $name => $value)
1711
-					{
1712
-						if ($value === null)
1713
-						{
1714
-							continue;
1715
-						}
1716
-
1717
-						$markup .= " $name=\"".self::escape($value).'"';
1718
-					}
1719
-				}
1720
-			}
1721
-
1722
-			$permitRawHtml = false;
1723
-
1724
-			if (isset($Element['text']))
1725
-			{
1726
-				$text = $Element['text'];
1727
-			}
1728
-			// very strongly consider an alternative if you're writing an
1729
-			// extension
1730
-			elseif (isset($Element['rawHtml']))
1731
-			{
1732
-				$text = $Element['rawHtml'];
1733
-
1734
-				$allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
1735
-				$permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
1736
-			}
1737
-
1738
-			$hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
1739
-
1740
-			if ($hasContent)
1741
-			{
1742
-				$markup .= $hasName ? '>' : '';
1743
-
1744
-				if (isset($Element['elements']))
1745
-				{
1746
-					$markup .= $this->elements($Element['elements']);
1747
-				}
1748
-				elseif (isset($Element['element']))
1749
-				{
1750
-					$markup .= $this->element($Element['element']);
1751
-				}
1752
-				else
1753
-				{
1754
-					if (!$permitRawHtml)
1755
-					{
1756
-						$markup .= self::escape($text, true);
1757
-					}
1758
-					else
1759
-					{
1760
-						$markup .= $text;
1761
-					}
1762
-				}
1763
-
1764
-				$markup .= $hasName ? '</' . $Element['name'] . '>' : '';
1765
-			}
1766
-			elseif ($hasName)
1767
-			{
1768
-				$markup .= ' />';
1769
-			}
1770
-
1771
-			return $markup;
1772
-		}
1773
-
1774
-		protected function elements(array $Elements)
1775
-		{
1776
-			$markup = '';
1777
-
1778
-			$autoBreak = true;
1779
-
1780
-			foreach ($Elements as $Element)
1781
-			{
1782
-				if (empty($Element))
1783
-				{
1784
-					continue;
1785
-				}
1786
-
1787
-				$autoBreakNext = (isset($Element['autobreak'])
1788
-					? $Element['autobreak'] : isset($Element['name'])
1789
-				);
1790
-				// (autobreak === false) covers both sides of an element
1791
-				$autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
1792
-
1793
-				$markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
1794
-				$autoBreak = $autoBreakNext;
1795
-			}
1796
-
1797
-			$markup .= $autoBreak ? "\n" : '';
1798
-
1799
-			return $markup;
1800
-		}
1801
-
1802
-		# ~
1803
-
1804
-		protected function li($lines)
1805
-		{
1806
-			$Elements = $this->linesElements($lines);
1807
-
1808
-			if ( ! in_array('', $lines)
1809
-			     and isset($Elements[0]) and isset($Elements[0]['name'])
1810
-			                                 and $Elements[0]['name'] === 'p'
1811
-			) {
1812
-				unset($Elements[0]['name']);
1813
-			}
1814
-
1815
-			return $Elements;
1816
-		}
1817
-
1818
-		#
1819
-		# AST Convenience
1820
-		#
1821
-
1822
-		/**
1823
-		 * Replace occurrences $regexp with $Elements in $text. Return an array of
1824
-		 * elements representing the replacement.
1825
-		 */
1826
-		protected static function pregReplaceElements($regexp, $Elements, $text)
1827
-		{
1828
-			$newElements = array();
1829
-
1830
-			while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
1831
-			{
1832
-				$offset = $matches[0][1];
1833
-				$before = substr($text, 0, $offset);
1834
-				$after = substr($text, $offset + strlen($matches[0][0]));
1835
-
1836
-				$newElements[] = array('text' => $before);
1837
-
1838
-				foreach ($Elements as $Element)
1839
-				{
1840
-					$newElements[] = $Element;
1841
-				}
1842
-
1843
-				$text = $after;
1844
-			}
1845
-
1846
-			$newElements[] = array('text' => $text);
1847
-
1848
-			return $newElements;
1849
-		}
1850
-
1851
-		#
1852
-		# Deprecated Methods
1853
-		#
1854
-
1855
-		function parse($text)
1856
-		{
1857
-			$markup = $this->text($text);
1858
-
1859
-			return $markup;
1860
-		}
1861
-
1862
-		protected function sanitiseElement(array $Element)
1863
-		{
1864
-			static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
1865
-			static $safeUrlNameToAtt  = array(
1866
-				'a'   => 'href',
1867
-				'img' => 'src',
1868
-			);
1869
-
1870
-			if ( ! isset($Element['name']))
1871
-			{
1872
-				unset($Element['attributes']);
1873
-				return $Element;
1874
-			}
1875
-
1876
-			if (isset($safeUrlNameToAtt[$Element['name']]))
1877
-			{
1878
-				$Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
1879
-			}
1880
-
1881
-			if ( ! empty($Element['attributes']))
1882
-			{
1883
-				foreach ($Element['attributes'] as $att => $val)
1884
-				{
1885
-					# filter out badly parsed attribute
1886
-					if ( ! preg_match($goodAttribute, $att))
1887
-					{
1888
-						unset($Element['attributes'][$att]);
1889
-					}
1890
-					# dump onevent attribute
1891
-					elseif (self::striAtStart($att, 'on'))
1892
-					{
1893
-						unset($Element['attributes'][$att]);
1894
-					}
1895
-				}
1896
-			}
1897
-
1898
-			return $Element;
1899
-		}
1900
-
1901
-		protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
1902
-		{
1903
-			foreach ($this->safeLinksWhitelist as $scheme)
1904
-			{
1905
-				if (self::striAtStart($Element['attributes'][$attribute], $scheme))
1906
-				{
1907
-					return $Element;
1908
-				}
1909
-			}
1910
-
1911
-			$Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
1912
-
1913
-			return $Element;
1914
-		}
1915
-
1916
-		#
1917
-		# Static Methods
1918
-		#
1919
-
1920
-		protected static function escape($text, $allowQuotes = false)
1921
-		{
1922
-			return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
1923
-		}
1924
-
1925
-		protected static function striAtStart($string, $needle)
1926
-		{
1927
-			$len = strlen($needle);
1928
-
1929
-			if ($len > strlen($string))
1930
-			{
1931
-				return false;
1932
-			}
1933
-			else
1934
-			{
1935
-				return strtolower(substr($string, 0, $len)) === strtolower($needle);
1936
-			}
1937
-		}
1938
-
1939
-		static function instance($name = 'default')
1940
-		{
1941
-			if (isset(self::$instances[$name]))
1942
-			{
1943
-				return self::$instances[$name];
1944
-			}
1945
-
1946
-			$instance = new static();
1947
-
1948
-			self::$instances[$name] = $instance;
1949
-
1950
-			return $instance;
1951
-		}
1952
-
1953
-		private static $instances = array();
1954
-
1955
-		#
1956
-		# Fields
1957
-		#
1958
-
1959
-		protected $DefinitionData;
1960
-
1961
-		#
1962
-		# Read-Only
1963
-
1964
-		protected $specialCharacters = array(
1965
-			'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
1966
-		);
1967
-
1968
-		protected $StrongRegex = array(
1969
-			'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
1970
-			'_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
1971
-		);
1972
-
1973
-		protected $EmRegex = array(
1974
-			'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1975
-			'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1976
-		);
1977
-
1978
-		protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
1979
-
1980
-		protected $voidElements = array(
1981
-			'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1982
-		);
1983
-
1984
-		protected $textLevelElements = array(
1985
-			'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1986
-			'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1987
-			'i', 'rp', 'del', 'code',          'strike', 'marquee',
1988
-			'q', 'rt', 'ins', 'font',          'strong',
1989
-			's', 'tt', 'kbd', 'mark',
1990
-			'u', 'xm', 'sub', 'nobr',
1991
-			'sup', 'ruby',
1992
-			'var', 'span',
1993
-			'wbr', 'time',
1994
-		);
1995
-	}
1199
+                    # compile the unmarked text
1200
+                    $InlineText = $this->inlineText($unmarkedText);
1201
+                    $Elements[] = $InlineText['element'];
1202
+
1203
+                    # compile the inline
1204
+                    $Elements[] = $this->extractElement($Inline);
1205
+
1206
+                    # remove the examined text
1207
+                    $text = substr($text, $Inline['position'] + $Inline['extent']);
1208
+
1209
+                    continue 2;
1210
+                }
1211
+
1212
+                # the marker does not belong to an inline
1213
+
1214
+                $unmarkedText = substr($text, 0, $markerPosition + 1);
1215
+
1216
+                $InlineText = $this->inlineText($unmarkedText);
1217
+                $Elements[] = $InlineText['element'];
1218
+
1219
+                $text = substr($text, $markerPosition + 1);
1220
+            }
1221
+
1222
+            $InlineText = $this->inlineText($text);
1223
+            $Elements[] = $InlineText['element'];
1224
+
1225
+            foreach ($Elements as &$Element)
1226
+            {
1227
+                if ( ! isset($Element['autobreak']))
1228
+                {
1229
+                    $Element['autobreak'] = false;
1230
+                }
1231
+            }
1232
+
1233
+            return $Elements;
1234
+        }
1235
+
1236
+        #
1237
+        # ~
1238
+        #
1239
+
1240
+        protected function inlineText($text)
1241
+        {
1242
+            $Inline = array(
1243
+                'extent' => strlen($text),
1244
+                'element' => array(),
1245
+            );
1246
+
1247
+            $Inline['element']['elements'] = self::pregReplaceElements(
1248
+                $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
1249
+                array(
1250
+                    array('name' => 'br'),
1251
+                    array('text' => "\n"),
1252
+                ),
1253
+                $text
1254
+            );
1255
+
1256
+            return $Inline;
1257
+        }
1258
+
1259
+        protected function inlineCode($Excerpt)
1260
+        {
1261
+            $marker = $Excerpt['text'][0];
1262
+
1263
+            if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
1264
+            {
1265
+                $text = $matches[2];
1266
+                $text = preg_replace('/[ ]*+\n/', ' ', $text);
1267
+
1268
+                return array(
1269
+                    'extent' => strlen($matches[0]),
1270
+                    'element' => array(
1271
+                        'name' => 'code',
1272
+                        'text' => $text,
1273
+                    ),
1274
+                );
1275
+            }
1276
+        }
1277
+
1278
+        protected function inlineEmailTag($Excerpt)
1279
+        {
1280
+            $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
1281
+
1282
+            $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
1283
+                                . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
1284
+
1285
+            if (strpos($Excerpt['text'], '>') !== false
1286
+                and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
1287
+            ){
1288
+                $url = $matches[1];
1289
+
1290
+                if ( ! isset($matches[2]))
1291
+                {
1292
+                    $url = "mailto:$url";
1293
+                }
1294
+
1295
+                return array(
1296
+                    'extent' => strlen($matches[0]),
1297
+                    'element' => array(
1298
+                        'name' => 'a',
1299
+                        'text' => $matches[1],
1300
+                        'attributes' => array(
1301
+                            'href' => $url,
1302
+                        ),
1303
+                    ),
1304
+                );
1305
+            }
1306
+        }
1307
+
1308
+        protected function inlineEmphasis($Excerpt)
1309
+        {
1310
+            if ( ! isset($Excerpt['text'][1]))
1311
+            {
1312
+                return;
1313
+            }
1314
+
1315
+            $marker = $Excerpt['text'][0];
1316
+
1317
+            if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1318
+            {
1319
+                $emphasis = 'strong';
1320
+            }
1321
+            elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1322
+            {
1323
+                $emphasis = 'em';
1324
+            }
1325
+            else
1326
+            {
1327
+                return;
1328
+            }
1329
+
1330
+            return array(
1331
+                'extent' => strlen($matches[0]),
1332
+                'element' => array(
1333
+                    'name' => $emphasis,
1334
+                    'handler' => array(
1335
+                        'function' => 'lineElements',
1336
+                        'argument' => $matches[1],
1337
+                        'destination' => 'elements',
1338
+                    )
1339
+                ),
1340
+            );
1341
+        }
1342
+
1343
+        protected function inlineEscapeSequence($Excerpt)
1344
+        {
1345
+            if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1346
+            {
1347
+                return array(
1348
+                    'element' => array('rawHtml' => $Excerpt['text'][1]),
1349
+                    'extent' => 2,
1350
+                );
1351
+            }
1352
+        }
1353
+
1354
+        protected function inlineImage($Excerpt)
1355
+        {
1356
+            if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1357
+            {
1358
+                return;
1359
+            }
1360
+
1361
+            $Excerpt['text']= substr($Excerpt['text'], 1);
1362
+
1363
+            $Link = $this->inlineLink($Excerpt);
1364
+
1365
+            if ($Link === null)
1366
+            {
1367
+                return;
1368
+            }
1369
+
1370
+            $Inline = array(
1371
+                'extent' => $Link['extent'] + 1,
1372
+                'element' => array(
1373
+                    'name' => 'img',
1374
+                    'attributes' => array(
1375
+                        'src' => $Link['element']['attributes']['href'],
1376
+                        'alt' => $Link['element']['handler']['argument'],
1377
+                    ),
1378
+                    'autobreak' => true,
1379
+                ),
1380
+            );
1381
+
1382
+            $Inline['element']['attributes'] += $Link['element']['attributes'];
1383
+
1384
+            unset($Inline['element']['attributes']['href']);
1385
+
1386
+            return $Inline;
1387
+        }
1388
+
1389
+        protected function inlineLink($Excerpt)
1390
+        {
1391
+            $Element = array(
1392
+                'name' => 'a',
1393
+                'handler' => array(
1394
+                    'function' => 'lineElements',
1395
+                    'argument' => null,
1396
+                    'destination' => 'elements',
1397
+                ),
1398
+                'nonNestables' => array('Url', 'Link'),
1399
+                'attributes' => array(
1400
+                    'href' => null,
1401
+                    'title' => null,
1402
+                ),
1403
+            );
1404
+
1405
+            $extent = 0;
1406
+
1407
+            $remainder = $Excerpt['text'];
1408
+
1409
+            if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1410
+            {
1411
+                $Element['handler']['argument'] = $matches[1];
1412
+
1413
+                $extent += strlen($matches[0]);
1414
+
1415
+                $remainder = substr($remainder, $extent);
1416
+            }
1417
+            else
1418
+            {
1419
+                return;
1420
+            }
1421
+
1422
+            if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
1423
+            {
1424
+                $Element['attributes']['href'] = $matches[1];
1425
+
1426
+                if (isset($matches[2]))
1427
+                {
1428
+                    $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1429
+                }
1430
+
1431
+                $extent += strlen($matches[0]);
1432
+            }
1433
+            else
1434
+            {
1435
+                if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1436
+                {
1437
+                    $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
1438
+                    $definition = strtolower($definition);
1439
+
1440
+                    $extent += strlen($matches[0]);
1441
+                }
1442
+                else
1443
+                {
1444
+                    $definition = strtolower($Element['handler']['argument']);
1445
+                }
1446
+
1447
+                if ( ! isset($this->DefinitionData['Reference'][$definition]))
1448
+                {
1449
+                    return;
1450
+                }
1451
+
1452
+                $Definition = $this->DefinitionData['Reference'][$definition];
1453
+
1454
+                $Element['attributes']['href'] = $Definition['url'];
1455
+                $Element['attributes']['title'] = $Definition['title'];
1456
+            }
1457
+
1458
+            return array(
1459
+                'extent' => $extent,
1460
+                'element' => $Element,
1461
+            );
1462
+        }
1463
+
1464
+        protected function inlineMarkup($Excerpt)
1465
+        {
1466
+            if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
1467
+            {
1468
+                return;
1469
+            }
1470
+
1471
+            if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
1472
+            {
1473
+                return array(
1474
+                    'element' => array('rawHtml' => $matches[0]),
1475
+                    'extent' => strlen($matches[0]),
1476
+                );
1477
+            }
1478
+
1479
+            if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
1480
+            {
1481
+                return array(
1482
+                    'element' => array('rawHtml' => $matches[0]),
1483
+                    'extent' => strlen($matches[0]),
1484
+                );
1485
+            }
1486
+
1487
+            if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
1488
+            {
1489
+                return array(
1490
+                    'element' => array('rawHtml' => $matches[0]),
1491
+                    'extent' => strlen($matches[0]),
1492
+                );
1493
+            }
1494
+        }
1495
+
1496
+        protected function inlineSpecialCharacter($Excerpt)
1497
+        {
1498
+            if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
1499
+                                                           and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
1500
+            ) {
1501
+                return array(
1502
+                    'element' => array('rawHtml' => '&' . $matches[1] . ';'),
1503
+                    'extent' => strlen($matches[0]),
1504
+                );
1505
+            }
1506
+
1507
+            return;
1508
+        }
1509
+
1510
+        protected function inlineStrikethrough($Excerpt)
1511
+        {
1512
+            if ( ! isset($Excerpt['text'][1]))
1513
+            {
1514
+                return;
1515
+            }
1516
+
1517
+            if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1518
+            {
1519
+                return array(
1520
+                    'extent' => strlen($matches[0]),
1521
+                    'element' => array(
1522
+                        'name' => 'del',
1523
+                        'handler' => array(
1524
+                            'function' => 'lineElements',
1525
+                            'argument' => $matches[1],
1526
+                            'destination' => 'elements',
1527
+                        )
1528
+                    ),
1529
+                );
1530
+            }
1531
+        }
1532
+
1533
+        protected function inlineUrl($Excerpt)
1534
+        {
1535
+            if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1536
+            {
1537
+                return;
1538
+            }
1539
+
1540
+            if (strpos($Excerpt['context'], 'http') !== false
1541
+                and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
1542
+            ) {
1543
+                $url = $matches[0][0];
1544
+
1545
+                $Inline = array(
1546
+                    'extent' => strlen($matches[0][0]),
1547
+                    'position' => $matches[0][1],
1548
+                    'element' => array(
1549
+                        'name' => 'a',
1550
+                        'text' => $url,
1551
+                        'attributes' => array(
1552
+                            'href' => $url,
1553
+                        ),
1554
+                    ),
1555
+                );
1556
+
1557
+                return $Inline;
1558
+            }
1559
+        }
1560
+
1561
+        protected function inlineUrlTag($Excerpt)
1562
+        {
1563
+            if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
1564
+            {
1565
+                $url = $matches[1];
1566
+
1567
+                return array(
1568
+                    'extent' => strlen($matches[0]),
1569
+                    'element' => array(
1570
+                        'name' => 'a',
1571
+                        'text' => $url,
1572
+                        'attributes' => array(
1573
+                            'href' => $url,
1574
+                        ),
1575
+                    ),
1576
+                );
1577
+            }
1578
+        }
1579
+
1580
+        # ~
1581
+
1582
+        protected function unmarkedText($text)
1583
+        {
1584
+            $Inline = $this->inlineText($text);
1585
+            return $this->element($Inline['element']);
1586
+        }
1587
+
1588
+        #
1589
+        # Handlers
1590
+        #
1591
+
1592
+        protected function handle(array $Element)
1593
+        {
1594
+            if (isset($Element['handler']))
1595
+            {
1596
+                if (!isset($Element['nonNestables']))
1597
+                {
1598
+                    $Element['nonNestables'] = array();
1599
+                }
1600
+
1601
+                if (is_string($Element['handler']))
1602
+                {
1603
+                    $function = $Element['handler'];
1604
+                    $argument = $Element['text'];
1605
+                    unset($Element['text']);
1606
+                    $destination = 'rawHtml';
1607
+                }
1608
+                else
1609
+                {
1610
+                    $function = $Element['handler']['function'];
1611
+                    $argument = $Element['handler']['argument'];
1612
+                    $destination = $Element['handler']['destination'];
1613
+                }
1614
+
1615
+                $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
1616
+
1617
+                if ($destination === 'handler')
1618
+                {
1619
+                    $Element = $this->handle($Element);
1620
+                }
1621
+
1622
+                unset($Element['handler']);
1623
+            }
1624
+
1625
+            return $Element;
1626
+        }
1627
+
1628
+        protected function handleElementRecursive(array $Element)
1629
+        {
1630
+            return $this->elementApplyRecursive(array($this, 'handle'), $Element);
1631
+        }
1632
+
1633
+        protected function handleElementsRecursive(array $Elements)
1634
+        {
1635
+            return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
1636
+        }
1637
+
1638
+        protected function elementApplyRecursive($closure, array $Element)
1639
+        {
1640
+            $Element = call_user_func($closure, $Element);
1641
+
1642
+            if (isset($Element['elements']))
1643
+            {
1644
+                $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
1645
+            }
1646
+            elseif (isset($Element['element']))
1647
+            {
1648
+                $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
1649
+            }
1650
+
1651
+            return $Element;
1652
+        }
1653
+
1654
+        protected function elementApplyRecursiveDepthFirst($closure, array $Element)
1655
+        {
1656
+            if (isset($Element['elements']))
1657
+            {
1658
+                $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
1659
+            }
1660
+            elseif (isset($Element['element']))
1661
+            {
1662
+                $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
1663
+            }
1664
+
1665
+            $Element = call_user_func($closure, $Element);
1666
+
1667
+            return $Element;
1668
+        }
1669
+
1670
+        protected function elementsApplyRecursive($closure, array $Elements)
1671
+        {
1672
+            foreach ($Elements as &$Element)
1673
+            {
1674
+                $Element = $this->elementApplyRecursive($closure, $Element);
1675
+            }
1676
+
1677
+            return $Elements;
1678
+        }
1679
+
1680
+        protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
1681
+        {
1682
+            foreach ($Elements as &$Element)
1683
+            {
1684
+                $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
1685
+            }
1686
+
1687
+            return $Elements;
1688
+        }
1689
+
1690
+        protected function element(array $Element)
1691
+        {
1692
+            if ($this->safeMode)
1693
+            {
1694
+                $Element = $this->sanitiseElement($Element);
1695
+            }
1696
+
1697
+            # identity map if element has no handler
1698
+            $Element = $this->handle($Element);
1699
+
1700
+            $hasName = isset($Element['name']);
1701
+
1702
+            $markup = '';
1703
+
1704
+            if ($hasName)
1705
+            {
1706
+                $markup .= '<' . $Element['name'];
1707
+
1708
+                if (isset($Element['attributes']))
1709
+                {
1710
+                    foreach ($Element['attributes'] as $name => $value)
1711
+                    {
1712
+                        if ($value === null)
1713
+                        {
1714
+                            continue;
1715
+                        }
1716
+
1717
+                        $markup .= " $name=\"".self::escape($value).'"';
1718
+                    }
1719
+                }
1720
+            }
1721
+
1722
+            $permitRawHtml = false;
1723
+
1724
+            if (isset($Element['text']))
1725
+            {
1726
+                $text = $Element['text'];
1727
+            }
1728
+            // very strongly consider an alternative if you're writing an
1729
+            // extension
1730
+            elseif (isset($Element['rawHtml']))
1731
+            {
1732
+                $text = $Element['rawHtml'];
1733
+
1734
+                $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
1735
+                $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
1736
+            }
1737
+
1738
+            $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
1739
+
1740
+            if ($hasContent)
1741
+            {
1742
+                $markup .= $hasName ? '>' : '';
1743
+
1744
+                if (isset($Element['elements']))
1745
+                {
1746
+                    $markup .= $this->elements($Element['elements']);
1747
+                }
1748
+                elseif (isset($Element['element']))
1749
+                {
1750
+                    $markup .= $this->element($Element['element']);
1751
+                }
1752
+                else
1753
+                {
1754
+                    if (!$permitRawHtml)
1755
+                    {
1756
+                        $markup .= self::escape($text, true);
1757
+                    }
1758
+                    else
1759
+                    {
1760
+                        $markup .= $text;
1761
+                    }
1762
+                }
1763
+
1764
+                $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
1765
+            }
1766
+            elseif ($hasName)
1767
+            {
1768
+                $markup .= ' />';
1769
+            }
1770
+
1771
+            return $markup;
1772
+        }
1773
+
1774
+        protected function elements(array $Elements)
1775
+        {
1776
+            $markup = '';
1777
+
1778
+            $autoBreak = true;
1779
+
1780
+            foreach ($Elements as $Element)
1781
+            {
1782
+                if (empty($Element))
1783
+                {
1784
+                    continue;
1785
+                }
1786
+
1787
+                $autoBreakNext = (isset($Element['autobreak'])
1788
+                    ? $Element['autobreak'] : isset($Element['name'])
1789
+                );
1790
+                // (autobreak === false) covers both sides of an element
1791
+                $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
1792
+
1793
+                $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
1794
+                $autoBreak = $autoBreakNext;
1795
+            }
1796
+
1797
+            $markup .= $autoBreak ? "\n" : '';
1798
+
1799
+            return $markup;
1800
+        }
1801
+
1802
+        # ~
1803
+
1804
+        protected function li($lines)
1805
+        {
1806
+            $Elements = $this->linesElements($lines);
1807
+
1808
+            if ( ! in_array('', $lines)
1809
+                 and isset($Elements[0]) and isset($Elements[0]['name'])
1810
+                                             and $Elements[0]['name'] === 'p'
1811
+            ) {
1812
+                unset($Elements[0]['name']);
1813
+            }
1814
+
1815
+            return $Elements;
1816
+        }
1817
+
1818
+        #
1819
+        # AST Convenience
1820
+        #
1821
+
1822
+        /**
1823
+         * Replace occurrences $regexp with $Elements in $text. Return an array of
1824
+         * elements representing the replacement.
1825
+         */
1826
+        protected static function pregReplaceElements($regexp, $Elements, $text)
1827
+        {
1828
+            $newElements = array();
1829
+
1830
+            while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
1831
+            {
1832
+                $offset = $matches[0][1];
1833
+                $before = substr($text, 0, $offset);
1834
+                $after = substr($text, $offset + strlen($matches[0][0]));
1835
+
1836
+                $newElements[] = array('text' => $before);
1837
+
1838
+                foreach ($Elements as $Element)
1839
+                {
1840
+                    $newElements[] = $Element;
1841
+                }
1842
+
1843
+                $text = $after;
1844
+            }
1845
+
1846
+            $newElements[] = array('text' => $text);
1847
+
1848
+            return $newElements;
1849
+        }
1850
+
1851
+        #
1852
+        # Deprecated Methods
1853
+        #
1854
+
1855
+        function parse($text)
1856
+        {
1857
+            $markup = $this->text($text);
1858
+
1859
+            return $markup;
1860
+        }
1861
+
1862
+        protected function sanitiseElement(array $Element)
1863
+        {
1864
+            static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
1865
+            static $safeUrlNameToAtt  = array(
1866
+                'a'   => 'href',
1867
+                'img' => 'src',
1868
+            );
1869
+
1870
+            if ( ! isset($Element['name']))
1871
+            {
1872
+                unset($Element['attributes']);
1873
+                return $Element;
1874
+            }
1875
+
1876
+            if (isset($safeUrlNameToAtt[$Element['name']]))
1877
+            {
1878
+                $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
1879
+            }
1880
+
1881
+            if ( ! empty($Element['attributes']))
1882
+            {
1883
+                foreach ($Element['attributes'] as $att => $val)
1884
+                {
1885
+                    # filter out badly parsed attribute
1886
+                    if ( ! preg_match($goodAttribute, $att))
1887
+                    {
1888
+                        unset($Element['attributes'][$att]);
1889
+                    }
1890
+                    # dump onevent attribute
1891
+                    elseif (self::striAtStart($att, 'on'))
1892
+                    {
1893
+                        unset($Element['attributes'][$att]);
1894
+                    }
1895
+                }
1896
+            }
1897
+
1898
+            return $Element;
1899
+        }
1900
+
1901
+        protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
1902
+        {
1903
+            foreach ($this->safeLinksWhitelist as $scheme)
1904
+            {
1905
+                if (self::striAtStart($Element['attributes'][$attribute], $scheme))
1906
+                {
1907
+                    return $Element;
1908
+                }
1909
+            }
1910
+
1911
+            $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
1912
+
1913
+            return $Element;
1914
+        }
1915
+
1916
+        #
1917
+        # Static Methods
1918
+        #
1919
+
1920
+        protected static function escape($text, $allowQuotes = false)
1921
+        {
1922
+            return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
1923
+        }
1924
+
1925
+        protected static function striAtStart($string, $needle)
1926
+        {
1927
+            $len = strlen($needle);
1928
+
1929
+            if ($len > strlen($string))
1930
+            {
1931
+                return false;
1932
+            }
1933
+            else
1934
+            {
1935
+                return strtolower(substr($string, 0, $len)) === strtolower($needle);
1936
+            }
1937
+        }
1938
+
1939
+        static function instance($name = 'default')
1940
+        {
1941
+            if (isset(self::$instances[$name]))
1942
+            {
1943
+                return self::$instances[$name];
1944
+            }
1945
+
1946
+            $instance = new static();
1947
+
1948
+            self::$instances[$name] = $instance;
1949
+
1950
+            return $instance;
1951
+        }
1952
+
1953
+        private static $instances = array();
1954
+
1955
+        #
1956
+        # Fields
1957
+        #
1958
+
1959
+        protected $DefinitionData;
1960
+
1961
+        #
1962
+        # Read-Only
1963
+
1964
+        protected $specialCharacters = array(
1965
+            '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
1966
+        );
1967
+
1968
+        protected $StrongRegex = array(
1969
+            '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
1970
+            '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
1971
+        );
1972
+
1973
+        protected $EmRegex = array(
1974
+            '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1975
+            '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1976
+        );
1977
+
1978
+        protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
1979
+
1980
+        protected $voidElements = array(
1981
+            'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1982
+        );
1983
+
1984
+        protected $textLevelElements = array(
1985
+            'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1986
+            'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1987
+            'i', 'rp', 'del', 'code',          'strike', 'marquee',
1988
+            'q', 'rt', 'ins', 'font',          'strong',
1989
+            's', 'tt', 'kbd', 'mark',
1990
+            'u', 'xm', 'sub', 'nobr',
1991
+            'sup', 'ruby',
1992
+            'var', 'span',
1993
+            'wbr', 'time',
1994
+        );
1995
+    }
1996 1996
 }
Please login to merge, or discard this patch.