value: microformats.org at 7 Last week the microformats.org community celebrated its 7th birthday at a gathering hosted by Mozilla in San Francisco and recognized accomplishments, challenges, and opportunities. The microformats tagline “humans first, machines second” forms the basis of many of our principles, and in that regard, we’d like to recognize a few people and thank them for their years of volunteer service Updated June 25th, 2012 by Tantek

value: San Francisco, CA


With dateline vcard

description: hnews

author: Glenn Jones

repo_name: spookie/gnu

license: mit


code:
microformats.org at 7

Last week the microformats.org community
                celebrated its 7th birthday at a gathering hosted by Mozilla in
                San Francisco and recognized accomplishments, challenges, and
                opportunities.

The microformats tagline “humans first, machines second”
                forms the basis of many of our
                principles, and
                in that regard, we’d like to recognize a few people and
                thank them for their years of volunteer service

Updated
            <time class="updated" datetime="2012-06-25T17:08:26">June 25th, 2012</time> by
            Tantek

        microformats.org


value: Tantek

name: microformats.org at 7 Last week the microformats.org community celebrated its 7th birthday at a gathering hosted by Mozilla in San Francisco and recognized accomplishments, challenges, and opportunities. The microformats tagline “humans first, machines second” forms the basis of many of our principles, and in that regard, we’d like to recognize a few people and thank them for their years of volunteer service Updated June 25th, 2012 by Tantek microformats.org


Minimum properties

description: hnews


hnews parsing tests

description: This page was design to test the parsing of hnews. These tests are part of the micorformats 2 test suite.


code:
Raspberry Pi

The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.

    More info about the Raspberry Pi

£29.95

            9.2 out of
            10
            based on 178 reviews

Categories:
        Computer,
        Education

From:
        The Raspberry Pi Foundation -

            Cambridge
            UK


value: The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.

value: 9.2 out of 10 based on 178 reviews

category: Computer Education

value: From: The Raspberry Pi Foundation - Cambridge UK

value: Cambridge UK


With h-review-aggregate

description: hproduct


Just a hyperlink

description: hproduct


Just a name

description: hproduct


code:
Raspberry Pi

The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It’s a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.

    More info about the Raspberry Pi

£29.95

4.5 out of 5

Categories:
        Computer,
        Education


value: 4.5 out of 5


With h-review

description: hproduct


hproduct parsing tests

description: This page was design to test the parsing of hproduct. These tests are part of the micorformats 2 test suite.


code:
Yorkshire Puddings

Makes 6 good sized Yorkshire puddings, the way my mum taught me

            4.5 stars out 5 based on
            35 reviews

Ingredients

1 egg

75g plain flour

70ml milk

60ml water

Pinch of salt

Time

Preparation 10 mins

Cook 25 mins

Instructions

Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole
            Yorkshire pudding tins and place in the oven to heat through.

To make the batter, add all the flour into a bowl and beat in the eggs until smooth.
            Gradually add the milk and water while beating the mixture. It should be smooth and
            without lumps. Finally add a pinch of salt.

Make sure the oil is piping hot before pouring the batter evenly into the tins.
            Place in the oven for 20-25 minutes until pudding have risen and look golden brown

Nutrition

Calories: 125

Fat: 3.2g

Cholesterol: 77mg

(Amount per pudding)

        Published on <time class="dt-published" datetime="2011-10-27">27 Oct 2011</time> by

            Glenn Jones

    Photo by dithie


summary: Makes 6 good sized Yorkshire puddings, the way my mum taught me

value: 4.5 stars out 5 based on 35 reviews

ingredient:
1 egg
75g plain flour
70ml milk
60ml water
Pinch of salt

instructions:
Pre-heat oven to 230C or gas mark 8. Pour the vegetable oil evenly into 2 x 4-hole
            Yorkshire pudding tins and place in the oven to heat through.

To make the batter, add all the flour into a bowl and beat in the eggs until smooth.
            Gradually add the milk and water while beating the mixture. It should be smooth and
            without lumps. Finally add a pinch of salt.

Make sure the oil is piping hot before pouring the batter evenly into the tins.
            Place in the oven for 20-25 minutes until pudding have risen and look golden brown

nutrition:
Calories: 125
Fat: 3.2g
Cholesterol: 77mg

value: Glenn Jones


Broken into properties

description: h-recipe


code:
Toast

Slice of bread

Butter


Minimum properties

description: h-recipe


h-recipe parsing tests

description: This page was design to test the parsing of h-recipe. These tests are part of the micorformats 2 test suite.


code:
        Tim Berners-Lee,
        invented the World Wide Web.

    Belongs to following groups:


value: Tim Berners-Lee

summary: invented the World Wide Web


Affiliations

description: h-resume


code:
Tim Berners-Lee

MIT

            32 Vassar Street,
            Room 32-G524,
            Cambridge,
            MA
            02139,
            USA.
            (Work)

Tel:+1 (617) 253 5702

Email:timbl@w3.org

Invented the World Wide Web.


value: Tim Berners-Lee MIT 32 Vassar Street, Room 32-G524, Cambridge, MA 02139, USA. (Work) Tel:+1 (617) 253 5702 Email:timbl@w3.org

summary: Invented the World Wide Web.


Contact

description: h-resume


code:
Tim Berners-Lee

Director of the World Wide Web Foundation

Invented the World Wide Web.

        The Queen's College, Oxford University,
        BA Hons (I) Physics
        <time class="dtstart" datetime="1973-09">1973</time> –
        <time class="dtend" datetime="1976-06">1976</time>


value: Tim Berners-Lee Director of the World Wide Web Foundation

summary: Invented the World Wide Web.

value: The Queen's College, Oxford University, BA Hons (I) Physics 1973 - 1976

type: h-event h-card

description: BA Hons (I) Physics

description: BA Hons (I) Physics

description: BA Hons (I) Physics


Educational experience

description: h-resume


code: Tim Berners-Lee, invented the World Wide Web.


Just a name

description: h-resume


code:
        Tim Berners-Lee,
        invented the World Wide Web.

    Skills:

information systems

advocacy

leadership


Skills

description: h-resume


hresume parsing tests

description: This page was design to test the parsing of hresume and its output to the newer JSON structure of micorformats 2. These tests are part of the micorformats 2 test suite.


code:
Tim Berners-Lee

Director of the World Wide Web Foundation

Invented the World Wide Web.

Director

World Wide Web Foundation

            <time class="dtstart" datetime="2009-01-18">Jan 2009</time> – Present
            <time class="duration" datetime="P2Y11M">(2 years 11 month)</time>


title: Director of the World Wide Web Foundation

summary: Invented the World Wide Web.

value: Director World Wide Web Foundation Jan 2009 - Present (2 years 11 month)

title: Director

title: Director


Work experience

description: h-resume


code:
Mediterranean Wraps

                433 S California Ave,
                Palo Alto,
                CA -

            (650) 321-8189

        9.2 out of
        10
        based on 17 reviews


type: h-item h-card

value: 433 S California Ave, Palo Alto, CA

name: Mediterranean Wraps 433 S California Ave, Palo Alto, CA - (650) 321-8189 9.2 out of 10 based on 17 reviews


With item as a vcard

description: hreview-aggregate


code:
        Mediterranean Wraps
     - Rated:
    4.5 out of 5 (6 reviews)


name: Mediterranean Wraps - Rated: 4.5 out of 5 (6 reviews)


Minimum properties

description: hreview-aggregate


hreview-aggregate parsing tests

description: This page was design to test the parsing of hreview-aggregate. These tests are part of the micorformats 2 test suite.


code:
Fullfrontal

A one day JavaScript Conference held in Brighton

<time class="dtstart" datetime="2012-11-09">9th November 2012</time>

        9.9 out of
        10
        based on 62 reviews


type: h-item h-event

name: Fullfrontal A one day JavaScript Conference held in Brighton 9th November 2012 9.9 out of 10 based on 62 reviews


With item as a vevent

description: hreview-aggregate


Just a hyperlink

description: hreview


code:
    Crepes on Cole

4.7 out of 5 stars


With implied item name and url

description: hreview


code:
        Crepes on Cole

5 out of 5 stars


With item

description: hreview


Just a name

description: hreview


code: <img class="hreview" src="images/photo.gif" alt="Crepes on Cole" />


Just a photo

description: hreview


hreview parsing tests

description: This page was design to test the parsing of hreview. These tests are part of the micorformats 2 test suite.


code:
    5 out of 5 stars

Crepes on Cole is awesome

        Reviewer: Tantek -

    <time class="reviewed" datetime="2005-04-18">April 18, 2005</time>

        Crepes on Cole is one of the best little
        creperies in San Francisco.
        Excellent food and service. Plenty of tables in a variety of sizes
        for parties large and small.  Window seating makes for excellent
        people watching to/from the N-Judah which stops right outside.
        I've had many fun social gatherings here, as well as gotten
        plenty of work done thanks to neighborhood WiFi.

Visit date: April 2005

Food eaten: crepe

Permanent link for review: http://example.com/crepe

Creative Commons Attribution-ShareAlike License


value: Reviewer: Tantek -

value: Crepes on Cole is one of the best little creperies in San Francisco. Excellent food and service. Plenty of tables in a variety of sizes for parties large and small. Window seating makes for excellent people watching to/from the N-Judah which stops right outside. I've had many fun social gatherings here, as well as gotten plenty of work done thanks to neighborhood WiFi.

value: San Francisco


With vcard item

description: hreview


code:
    Brendan Eich

    Mitchell Baker

Mozilla

    665 3rd St.
    Suite 207
    San Francisco,
    CA
    94107
    U.S.A.


value: 665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A.


itemref include pattern - h-card

description: h-card


code:
    Monetizing Android Apps - spaekers:
    Chrix Finne,
    Kenneth Lui -

        Room 10

    New Low-Level Media APIs in Android - spaekers:
    Dave Burke -

        Room 11

    Session 01 is between:
    <time class="dt-start" datetime="2012-06-27T15:45:00-0800">3:45PM</time> to
    <time class="dt-end" datetime="2012-06-27T16:45:00-0800">4:45PM</time>

    Moscone Center,
    San Francisco


speaker:
Chrix Finne
Kenneth Lui

value: Room 10 Moscone Center, San Francisco

extended-address:
Room 10
Moscone Center

value: Room 11 Moscone Center, San Francisco

extended-address:
Room 11
Moscone Center


itemref include pattern - h-event

description: h-card


code:
    Ben Ward
    Twitter

    Dan Webb
    Twitter

Twitter

        1355 Market St,
        San Francisco,
        CA
        94103


value: 1355 Market St, San Francisco, CA 94103


Hyperlink header include pattern

description: h-card


code:
    HTML5 & CSS3 latest features in action! -
    David Rousset -
    <time class="dt-start" datetime="2012-10-30T11:45:00-08:00">Tue 11:45am</time>
    <object data="#buildconf" class="include" type="text/html" height="0" width="0"></object>

    Building High-Performing JavaScript for Modern Engines -
    John-David Dalton and
    Amanda Silver -
    <time class="dt-start" datetime="2012-10-31T11:15:00-08:00">Wed 11:15am</time>
    <object data="#buildconf" class="include" type="text/html" height="0" width="0"></object>

Build Conference

        Redmond,
        Washington,
        USA


summary: Build Conference

value: Redmond, Washington, USA

name: Building High-Performing JavaScript for Modern Engines

speaker:
John-David Dalton
Amanda Silver

summary: Build Conference


Object include pattern

description: h-card


include pattern tests

description: This page was design to test the parsing of includes. These tests are part of the micorformats 2 test suite.


code:
        Opera

        Chris Mills

        Erik Möller


Table header include pattern

description: h-card


code:
        Mozilla Foundation

        665 3rd St.
        Suite 207
        San Francisco,
        CA
        94107
        U.S.A.


name: 665 3rd St. Suite 207 San Francisco, CA 94107 U.S.A.


name: Mixed propertries form different versions

description: h-card


summary: Invented the World Wide Web.

title: Director

title: Director


Mixed roots form different versions

description: h-resume


h-adr parsing tests

description: This page was design to test the parsing of mark-up which mixes version 1 and 2 microformats. These tests are part of the micorformats 2 test suite.


Roots from two versions

description: h-card


code: <link rel="updates alternate" type="application/atom+xml" href="updates.atom" />


type: application/atom+xml

rel: updates


A rel=alternate

description: rel=alternate


A rel=license

description: rel=license


A rel=nofollow

description: rel=nofollow


rel=* and xfn parsing tests

description: This page was design to test the parsing of rel=* and xfn. These tests are part of the micorformats 2 test suite.


code:
jane

jeo

lily

oliver

emily

jack

isabella

harry

sophia

charlie

olivia

james

ava

joshua

chloe

alfie

isla


A xfn all properties

description: xfn


code:
twitter

delicious

google+

lanyrd

github

flickr

linkedin

slideshare


A xfn elsewhere list

description: xfn


name: Expanding URLs in e-* properties parsing tests

description: This page was design to test the expanding of relative URLs to absolute URLs in the HTML content of e-* propreties. These tests are part of the micorformats 2 test suite.


code:
Expanding URLs within HTML content

Should not change: http://www.w3.org/

Should not change: http://example.com/

File relative: test.html = http://example.com/test.html

Directory relative: /test/test.html = http://example.com/test/test.html

Relative to root: /test.html = http://example.com/test.html


value: Should not change: http://www.w3.org/ Should not change: http://example.com/ File relative: test.html = http://example.com/test.html Directory relative: /test/test.html = http://example.com/test/test.html Relative to root: /test.html = http://example.com/test.html


Expanding URLs in e-* properties

description: h-entry


code:
<?php

/**
 * microformats test suite for php-mf2
 *
 * Before running this test suite, ensure that you run composer install
 * to install the microformats/tests repository of tests.
 * @see https://github.com/microformats/tests
 *
 * CLI usage from library root directory: php ./tests/test-suite/test-suite.php
 *
 * This will look through the test directories for .html files that
 * represent a test and the corresponding .json file that represent
 * the expected output. If a test fails, a message is displayed along
 * with the parsed output and the expected output, both in array format.
 *
 * Individual test suites may be run by specifying the relative path within the repo.
 * For example, to run only the 'microformats-v2' tests:
 *   php ./tests/test-suite/test-suite.php microformats-v2
 */

namespace Mf2\Parser\TestSuite;

use Mf2\Parser;
use Mf2;

error_reporting(E_ALL);
require dirname(__DIR__) . '/../vendor/autoload.php';

class TestSuite
{
	private $path = '';

	private $suites;

	private $tests_total = 0;

	private $tests_passed = 0;

	private $tests_failed = 0;

	/**
	 * This method constructs the TestSuite
	 * @param string $path: path to test-suite-data
	 * @access public
	 */
	public function __construct($path = '')
	{
		$path = './vendor/mf2/tests/tests/' . $path;

		if ( !file_exists($path) )
		{
			echo sprintf('Specified path was not found: %s', $path), "\n";
			exit;
		}

		$this->path = $path;
	} # end method __construct()

	/**
	 * This method runs the test suite
	 * @param array
	 * @access public
	 * @return bool
	 */
	public function start()
	{
		$directory = new \RecursiveDirectoryIterator($this->path, \RecursiveDirectoryIterator::SKIP_DOTS);
		$iterator = new \RecursiveIteratorIterator($directory);
		$this->suites = new \RegexIterator($iterator, '/^.+\.html$/i', \RecursiveRegexIterator::GET_MATCH);

		foreach ( $this->suites as $suite )
		{
			$this->runTest(reset($suite));
		}

		echo sprintf('Total tests: %d', $this->tests_total), "\n";
		echo sprintf('Passed: %d', $this->tests_passed), "\n";
		echo sprintf('Failed: %d', $this->tests_failed), "\n";

		return TRUE;
	} # end method start()

	/**
	 * This method handles running a test
	 * @param string $path: path to the test's HTML file
	 * @access public
	 * @return bool
	 */
	public function runTest($path)
	{
		$test_name = basename($path, '.html');
		echo sprintf('Running test: %s.', $test_name), "\n";

		$dirname = dirname($path);
		$json_file = $dirname . '/' . $test_name . '.json';

		if ( !file_exists($json_file) )
		{
			echo sprintf('Halting test: %s. No json file found.', $test_name), "\n";
			return FALSE;
		}

		$input = file_get_contents($path);
		$expected_output = json_decode(file_get_contents($json_file), TRUE);

		$parser = new Parser($input, '', TRUE);
		$output = $parser->parse(TRUE);

		$test_differences = $this->array_diff_assoc_recursive($expected_output, $output);

		# if: test passed
		if ( empty($test_differences) )
		{
			$this->tests_passed++;
		}
		# else: test failed
		else
		{
			echo sprintf('Test failed: %s', $test_name), "\n\n";
			echo sprintf('Parsed: %s', print_r($output, TRUE)), "\n";
			echo sprintf('Expected: %s', print_r($expected_output, TRUE)), "\n";
			echo sprintf('Differences: %s', print_r($test_differences, TRUE)), "\n";
			$this->tests_failed++;
		} # end if

		return TRUE;
	} # end method runTest()

	/**
	 * This method recursively compares two arrays and returns the difference
	 * @see http://us2.php.net/manual/en/function.array-diff-assoc.php
	 * @param array $array1
	 * @param array $array2
	 * @access public
	 * @return array
	 */
	public function array_diff_assoc_recursive($array1, $array2, $canonicalize = true)
	{
		$difference = array();

		# loop: each key in first array
		foreach ( $array1 as $key => $value )
		{

			# if: nested array
			if ( is_array($value) )
			{

				# if: mis-match
				if ( !isset($array2[$key]) || !is_array($array2[$key]) )
				{
					$difference[$key] = $value;
				}
				# else: recursive
				else
				{
					$recursive_diff = $this->array_diff_assoc_recursive($value, $array2[$key]);

					if ( !empty($recursive_diff) )
					{
						$difference[$key] = $recursive_diff;
					}

				}

			}
			# else if: numeric key, non-array value
			else if ( is_numeric($key) && !is_array($value) )
			{

				# if: check for value anywhere in second array (JSON is orderless)
				if ( !in_array($value, $array2) )
				{
					$difference[$key] = $value;
				}

			}
			# else if: associative key
			else if ( !array_key_exists($key, $array2) || $array2[$key] !== $value )
			{
				$difference[$key] = $value;
			}

		} # end loop

		return $difference;
	} # end method array_diff_assoc_recursive()

	/**
	 * DEPRECATED
	 * This method handles running a test suite
	 * @param string $path: path to the suite's JSON file
	 * @access public
	 * @return bool
	 */
	public function runSuite($path)
	{
		$suite = json_decode(file_get_contents($path));
		echo sprintf('Running %s.', $suite->name), "\n";

		$iterator = new \DirectoryIterator(dirname($path));

		# loop: each file in the test suite
		foreach ( $iterator as $file )
		{

			# if: file is a sub-directory and not a dot-directory
			if ( $file->isDir() && !$file->isDot() )
			{
				$this->tests_total++;

				$path_of_test = $file->getPathname() . '/';

				$test = json_decode(file_get_contents($path_of_test . 'test.json'));
				$input = file_get_contents($path_of_test . 'input.html');
				$expected_output = json_decode(file_get_contents($path_of_test . 'output.json'), TRUE);

				$parser = new Parser($input, '', TRUE);
				$output = $parser->parse(TRUE);

				# if: test passed
				if ( $output['items'] === $expected_output['items'] )
				{
					// echo '.'; # can output a dot for successful tests
					$this->tests_passed++;
				}
				# else: test failed
				else
				{
					echo sprintf('"%s" failed.', $test->name), "\n\n";
					echo sprintf('Parsed: %s', print_r($output, TRUE)), "\n";
					echo sprintf('Expected: %s', print_r($expected_output, TRUE)), "\n";
					$this->tests_failed++;
				} # end if

			} # end if

		} # end loop

		return TRUE;
	} # end method runSuite()

}

$path = ( empty($argv[1]) ) ? '' : $argv[1];
$TestSuite = new TestSuite($path);
$TestSuite->start(); # run tests


code:
*~
/composer.lock
/vendor/


code:
<?php

// Use this file if you cannot use class autoloading. It will include all the
// files needed for the Markdown parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.

require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';


code:
<?php
/**
 * Markdown  -  A text-to-HTML conversion tool for web writers
 *
 * @package   php-markdown
 * @author    Michel Fortin <michel.fortin@michelf.com>
 * @copyright 2004-2018 Michel Fortin <https://michelf.com/projects/php-markdown/>
 * @copyright (Original Markdown) 2004-2006 John Gruber <https://daringfireball.net/projects/markdown/>
 */

namespace Michelf;

/**
 * Markdown Parser Class
 */
class Markdown implements MarkdownInterface {
	/**
	 * Define the package version
	 * @var string
	 */
	const MARKDOWNLIB_VERSION = "1.8.0";

	/**
	 * Simple function interface - Initialize the parser and return the result
	 * of its transform method. This will work fine for derived classes too.
	 *
	 * @api
	 *
	 * @param  string $text
	 * @return string
	 */
	public static function defaultTransform($text) {
		// Take parser class on which this function was called.
		$parser_class = \get_called_class();

		// Try to take parser from the static parser list
		static $parser_list;
		$parser =& $parser_list[$parser_class];

		// Create the parser it not already set
		if (!$parser) {
			$parser = new $parser_class;
		}

		// Transform text using parser.
		return $parser->transform($text);
	}

	/**
	 * Configuration variables
	 */

	/**
	 * Change to ">" for HTML output.
	 * @var string
	 */
	public $empty_element_suffix = " />";

	/**
	 * The width of indentation of the output markup
	 * @var int
	 */
	public $tab_width = 4;

	/**
	 * Change to `true` to disallow markup or entities.
	 * @var boolean
	 */
	public $no_markup   = false;
	public $no_entities = false;

	/**
	 * Change to `true` to enable line breaks on \n without two trailling spaces
	 * @var boolean
	 */
	public $hard_wrap = false;

	/**
	 * Predefined URLs and titles for reference links and images.
	 * @var array
	 */
	public $predef_urls   = array();
	public $predef_titles = array();

	/**
	 * Optional filter function for URLs
	 * @var callable
	 */
	public $url_filter_func = null;

	/**
	 * Optional header id="" generation callback function.
	 * @var callable
	 */
	public $header_id_func = null;

	/**
	 * Optional function for converting code block content to HTML
	 * @var callable
	 */
	public $code_block_content_func = null;

	/**
	 * Optional function for converting code span content to HTML.
	 * @var callable
	 */
	public $code_span_content_func = null;

	/**
	 * Class attribute to toggle "enhanced ordered list" behaviour
	 * setting this to true will allow ordered lists to start from the index
	 * number that is defined first.
	 *
	 * For example:
	 * 2. List item two
	 * 3. List item three
	 *
	 * Becomes:
	 *

	 *
List item two

	 *
List item three

	 *

	 *
	 * @var bool
	 */
	public $enhanced_ordered_list = false;

	/**
	 * Parser implementation
	 */

	/**
	 * Regex to match balanced [brackets].
	 * Needed to insert a maximum bracked depth while converting to PHP.
	 * @var int
	 */
	protected $nested_brackets_depth = 6;
	protected $nested_brackets_re;

	protected $nested_url_parenthesis_depth = 4;
	protected $nested_url_parenthesis_re;

	/**
	 * Table of hash values for escaped characters:
	 * @var string
	 */
	protected $escape_chars = '\`*_{}[]()>#+-.!';
	protected $escape_chars_re;

	/**
	 * Constructor function. Initialize appropriate member variables.
	 * @return void
	 */
	public function __construct() {
		$this->_initDetab();
		$this->prepareItalicsAndBold();

		$this->nested_brackets_re =
			str_repeat('(?>[^\[\]]+|\[', $this->nested_brackets_depth).
			str_repeat('\])*', $this->nested_brackets_depth);

		$this->nested_url_parenthesis_re =
			str_repeat('(?>[^()\s]+|\(', $this->nested_url_parenthesis_depth).
			str_repeat('(?>\)))*', $this->nested_url_parenthesis_depth);

		$this->escape_chars_re = '['.preg_quote($this->escape_chars).']';

		// Sort document, block, and span gamut in ascendent priority order.
		asort($this->document_gamut);
		asort($this->block_gamut);
		asort($this->span_gamut);
	}

	/**
	 * Internal hashes used during transformation.
	 * @var array
	 */
	protected $urls        = array();
	protected $titles      = array();
	protected $html_hashes = array();

	/**
	 * Status flag to avoid invalid nesting.
	 * @var boolean
	 */
	protected $in_anchor = false;

	/**
	 * Status flag to avoid invalid nesting.
	 * @var boolean
	 */
	protected $in_emphasis_processing = false;

	/**
	 * Called before the transformation process starts to setup parser states.
	 * @return void
	 */
	protected function setup() {
		// Clear global hashes.
		$this->urls        = $this->predef_urls;
		$this->titles      = $this->predef_titles;
		$this->html_hashes = array();
		$this->in_anchor   = false;
		$this->in_emphasis_processing = false;
	}

	/**
	 * Called after the transformation process to clear any variable which may
	 * be taking up memory unnecessarly.
	 * @return void
	 */
	protected function teardown() {
		$this->urls        = array();
		$this->titles      = array();
		$this->html_hashes = array();
	}

	/**
	 * Main function. Performs some preprocessing on the input text and pass
	 * it through the document gamut.
	 *
	 * @api
	 *
	 * @param  string $text
	 * @return string
	 */
	public function transform($text) {
		$this->setup();

		# Remove UTF-8 BOM and marker character in input, if present.
		$text = preg_replace('{^\xEF\xBB\xBF|\x1A}', '', $text);

		# Standardize line endings:
		#   DOS to Unix and Mac to Unix
		$text = preg_replace('{\r\n?}', "\n", $text);

		# Make sure $text ends with a couple of newlines:
		$text .= "\n\n";

		# Convert all tabs to spaces.
		$text = $this->detab($text);

		# Turn block-level HTML blocks into hash entries
		$text = $this->hashHTMLBlocks($text);

		# Strip any lines consisting only of spaces and tabs.
		# This makes subsequent regexen easier to write, because we can
		# match consecutive blank lines with /\n+/ instead of something
		# contorted like /[ ]*\n+/ .
		$text = preg_replace('/^[ ]+$/m', '', $text);

		# Run document gamut methods.
		foreach ($this->document_gamut as $method => $priority) {
			$text = $this->$method($text);
		}

		$this->teardown();

		return $text . "\n";
	}

	/**
	 * Define the document gamut
	 * @var array
	 */
	protected $document_gamut = array(
		// Strip link definitions, store in hashes.
		"stripLinkDefinitions" => 20,
		"runBasicBlockGamut"   => 30,
	);

	/**
	 * Strips link definitions from text, stores the URLs and titles in
	 * hash references
	 * @param  string $text
	 * @return string
	 */
	protected function stripLinkDefinitions($text) {

		$less_than_tab = $this->tab_width - 1;

		// Link defs are in the form: ^[id]: url "optional title"
		$text = preg_replace_callback('{
							^[ ]{0,'.$less_than_tab.'}\[(.+)\][ ]?:	# id = $1
							  [ ]*
							  \n?				# maybe *one* newline
							  [ ]*
							(?:
							  <(.+?)>			# url = $2
							|
							  (\S+?)			# url = $3
							)
							  [ ]*
							  \n?				# maybe one newline
							  [ ]*
							(?:
								(?<=\s)			# lookbehind for whitespace
								["(]
								(.*?)			# title = $4
								[")]
								[ ]*
							)?	# title is optional
							(?:\n+|\Z)
			}xm',
			array($this, '_stripLinkDefinitions_callback'),
			$text
		);
		return $text;
	}

	/**
	 * The callback to strip link definitions
	 * @param  array $matches
	 * @return string
	 */
	protected function _stripLinkDefinitions_callback($matches) {
		$link_id = strtolower($matches[1]);
		$url = $matches[2] == '' ? $matches[3] : $matches[2];
		$this->urls[$link_id] = $url;
		$this->titles[$link_id] =& $matches[4];
		return ''; // String that will replace the block
	}

	/**
	 * Hashify HTML blocks
	 * @param  string $text
	 * @return string
	 */
	protected function hashHTMLBlocks($text) {
		if ($this->no_markup) {
			return $text;
		}

		$less_than_tab = $this->tab_width - 1;

		/**
		 * Hashify HTML blocks:
		 *
		 * We only want to do this for block-level HTML tags, such as headers,
		 * lists, and tables. That's because we still want to wrap
s around
		 * "paragraphs" that are wrapped in non-block-level tags, such as
		 * anchors, phrase emphasis, and spans. The list of tags we're looking
		 * for is hard-coded:
		 *
		 * *  List "a" is made of tags which can be both inline or block-level.
		 *    These will be treated block-level when the start tag is alone on
		 *    its line, otherwise they're not matched here and will be taken as
		 *    inline later.
		 * *  List "b" is made of tags which are always block-level;
		 */
		$block_tags_a_re = 'ins|del';
		$block_tags_b_re = 'p|div|h[1-6]|blockquote|pre|table|dl|ol|ul|address|'.
						   'script|noscript|style|form|fieldset|iframe|math|svg|'.
						   'article|section|nav|aside|hgroup|header|footer|'.
						   'figure';

		// Regular expression for the content of a block tag.
		$nested_tags_level = 4;
		$attr = '
			(?>				# optional tag attributes
			  \s			# starts with whitespace
			  (?>
				[^>"/]+		# text outside quotes
			  |
				/+(?!>)		# slash not followed by ">"
			  |
				"[^"]*"		# text inside double quotes (tolerate ">")
			  |
				\'[^\']*\'	# text inside single quotes (tolerate ">")
			  )*
			)?
			';
		$content =
			str_repeat('
				(?>
				  [^<]+			# content without tag
				|
				  <\2			# nested opening tag
					'.$attr.'	# attributes
					(?>
					  />
					|
					  >', $nested_tags_level).	// end of opening tag
					  '.*?'.					// last level nested tag content
			str_repeat('
					  </\2\s*>	# closing nested tag
					)
				  |
					<(?!/\2\s*>	# other tags with a different name
				  )
				)*',
				$nested_tags_level);
		$content2 = str_replace('\2', '\3', $content);

		/**
		 * First, look for nested blocks, e.g.:
		 *

		 *

		 * 		tags for inner block must be indented.
		 *

		 *

		 *
		 * The outermost tags must start at the left margin for this to match,
		 * and the inner nested divs must be indented.
		 * We need to do this before the next, more liberal match, because the
		 * next match will start at the first `<div>` and stop at the
		 * first `</div>`.
		 */
		$text = preg_replace_callback('{(?>
			(?>
				(?<=\n)			# Starting on its own line
				|				# or
				\A\n?			# the at beginning of the doc
			)
			(						# save in $1

			  # Match from `\n<tag>` to `</tag>\n`, handling nested tags
			  # in between.

						[ ]{0,'.$less_than_tab.'}
						<('.$block_tags_b_re.')# start tag = $2
						'.$attr.'>			# attributes followed by > and \n
						'.$content.'		# content, support nesting
						</\2>				# the matching end tag
						[ ]*				# trailing spaces/tabs
						(?=\n+|\Z)	# followed by a newline or end of document

			| # Special version for tags of group a.

						[ ]{0,'.$less_than_tab.'}
						<('.$block_tags_a_re.')# start tag = $3
						'.$attr.'>[ ]*\n	# attributes followed by >
						'.$content2.'		# content, support nesting
						</\3>				# the matching end tag
						[ ]*				# trailing spaces/tabs
						(?=\n+|\Z)	# followed by a newline or end of document

			| # Special case just for
. It was easier to make a special
			  # case than to make the other regex more complicated.

						[ ]{0,'.$less_than_tab.'}
						<(hr)				# start tag = $2
						'.$attr.'			# attributes
						/?>					# the matching end tag
						[ ]*
						(?=\n{2,}|\Z)		# followed by a blank line or end of document

			| # Special case for standalone HTML comments:

					[ ]{0,'.$less_than_tab.'}
					(?s:

					)
					[ ]*
					(?=\n{2,}|\Z)		# followed by a blank line or end of document

			| # PHP and ASP-style processor instructions (<? and <%)

					[ ]{0,'.$less_than_tab.'}
					(?s:
						<([?%])			# $2
						.*?
						\2>
					)
					[ ]*
					(?=\n{2,}|\Z)		# followed by a blank line or end of document

			)
			)}Sxmi',
			array($this, '_hashHTMLBlocks_callback'),
			$text
		);

		return $text;
	}

	/**
	 * The callback for hashing HTML blocks
	 * @param  string $matches
	 * @return string
	 */
	protected function _hashHTMLBlocks_callback($matches) {
		$text = $matches[1];
		$key  = $this->hashBlock($text);
		return "\n\n$key\n\n";
	}

	/**
	 * Called whenever a tag must be hashed when a function insert an atomic
	 * element in the text stream. Passing $text to through this function gives
	 * a unique text-token which will be reverted back when calling unhash.
	 *
	 * The $boundary argument specify what character should be used to surround
	 * the token. By convension, "B" is used for block elements that needs not
	 * to be wrapped into paragraph tags at the end, ":" is used for elements
	 * that are word separators and "X" is used in the general case.
	 *
	 * @param  string $text
	 * @param  string $boundary
	 * @return string
	 */
	protected function hashPart($text, $boundary = 'X') {
		// Swap back any tag hash found in $text so we do not have to `unhash`
		// multiple times at the end.
		$text = $this->unhash($text);

		// Then hash the block.
		static $i = 0;
		$key = "$boundary\x1A" . ++$i . $boundary;
		$this->html_hashes[$key] = $text;
		return $key; // String that will replace the tag.
	}

	/**
	 * Shortcut function for hashPart with block-level boundaries.
	 * @param  string $text
	 * @return string
	 */
	protected function hashBlock($text) {
		return $this->hashPart($text, 'B');
	}

	/**
	 * Define the block gamut - these are all the transformations that form
	 * block-level tags like paragraphs, headers, and list items.
	 * @var array
	 */
	protected $block_gamut = array(
		"doHeaders"         => 10,
		"doHorizontalRules" => 20,
		"doLists"           => 40,
		"doCodeBlocks"      => 50,
		"doBlockQuotes"     => 60,
	);

	/**
	 * Run block gamut tranformations.
	 *
	 * We need to escape raw HTML in Markdown source before doing anything
	 * else. This need to be done for each block, and not only at the
	 * begining in the Markdown function since hashed blocks can be part of
	 * list items and could have been indented. Indented blocks would have
	 * been seen as a code block in a previous pass of hashHTMLBlocks.
	 *
	 * @param  string $text
	 * @return string
	 */
	protected function runBlockGamut($text) {
		$text = $this->hashHTMLBlocks($text);
		return $this->runBasicBlockGamut($text);
	}

	/**
	 * Run block gamut tranformations, without hashing HTML blocks. This is
	 * useful when HTML blocks are known to be already hashed, like in the first
	 * whole-document pass.
	 *
	 * @param  string $text
	 * @return string
	 */
	protected function runBasicBlockGamut($text) {

		foreach ($this->block_gamut as $method => $priority) {
			$text = $this->$method($text);
		}

		// Finally form paragraph and restore hashed blocks.
		$text = $this->formParagraphs($text);

		return $text;
	}

	/**
	 * Convert horizontal rules
	 * @param  string $text
	 * @return string
	 */
	protected function doHorizontalRules($text) {
		return preg_replace(
			'{
				^[ ]{0,3}	# Leading space
				([-*_])		# $1: First marker
				(?>			# Repeated marker group
					[ ]{0,2}	# Zero, one, or two spaces.
					\1			# Marker character
				){2,}		# Group repeated at least twice
				[ ]*		# Tailing spaces
				$			# End of line.
			}mx',
			"\n".$this->hashBlock("
empty_element_suffix")."\n",
			$text
		);
	}

	/**
	 * These are all the transformations that occur *within* block-level
	 * tags like paragraphs, headers, and list items.
	 * @var array
	 */
	protected $span_gamut = array(
		// Process character escapes, code spans, and inline HTML
		// in one shot.
		"parseSpan"           => -30,
		// Process anchor and image tags. Images must come first,
		// because ![foo][f] looks like an anchor.
		"doImages"            =>  10,
		"doAnchors"           =>  20,
		// Make links out of things like `<https://example.com/>`
		// Must come after doAnchors, because you can use < and >
		// delimiters in inline links like [this](<url>).
		"doAutoLinks"         =>  30,
		"encodeAmpsAndAngles" =>  40,
		"doItalicsAndBold"    =>  50,
		"doHardBreaks"        =>  60,
	);

	/**
	 * Run span gamut transformations
	 * @param  string $text
	 * @return string
	 */
	protected function runSpanGamut($text) {
		foreach ($this->span_gamut as $method => $priority) {
			$text = $this->$method($text);
		}

		return $text;
	}

	/**
	 * Do hard breaks
	 * @param  string $text
	 * @return string
	 */
	protected function doHardBreaks($text) {
		if ($this->hard_wrap) {
			return preg_replace_callback('/ *\n/',
				array($this, '_doHardBreaks_callback'), $text);
		} else {
			return preg_replace_callback('/ {2,}\n/',
				array($this, '_doHardBreaks_callback'), $text);
		}
	}

	/**
	 * Trigger part hashing for the hard break (callback method)
	 * @param  array $matches
	 * @return string
	 */
	protected function _doHardBreaks_callback($matches) {
		return $this->hashPart("
empty_element_suffix\n");
	}

	/**
	 * Turn Markdown link shortcuts into XHTML  tags.
	 * @param  string $text
	 * @return string
	 */
	protected function doAnchors($text) {
		if ($this->in_anchor) {
			return $text;
		}
		$this->in_anchor = true;

		// First, handle reference-style links: [link text] [id]
		$text = preg_replace_callback('{
			(					# wrap whole match in $1
			  \[
				('.$this->nested_brackets_re.')	# link text = $2
			  \]

			  [ ]?				# one optional space
			  (?:\n[ ]*)?		# one optional newline followed by spaces

			  \[
				(.*?)		# id = $3
			  \]
			)
			}xs',
			array($this, '_doAnchors_reference_callback'), $text);

		// Next, inline-style links: [link text](url "optional title")
		$text = preg_replace_callback('{
			(				# wrap whole match in $1
			  \[
				('.$this->nested_brackets_re.')	# link text = $2
			  \]
			  \(			# literal paren
				[ \n]*
				(?:
					<(.+?)>	# href = $3
				|
					('.$this->nested_url_parenthesis_re.')	# href = $4
				)
				[ \n]*
				(			# $5
				  ([\'"])	# quote char = $6
				  (.*?)		# Title = $7
				  \6		# matching quote
				  [ \n]*	# ignore any spaces/tabs between closing quote and )
				)?			# title is optional
			  \)
			)
			}xs',
			array($this, '_doAnchors_inline_callback'), $text);

		// Last, handle reference-style shortcuts: [link text]
		// These must come last in case you've also got [link text][1]
		// or [link text](/foo)
		$text = preg_replace_callback('{
			(					# wrap whole match in $1
			  \[
				([^\[\]]+)		# link text = $2; can\'t contain [ or ]
			  \]
			)
			}xs',
			array($this, '_doAnchors_reference_callback'), $text);

		$this->in_anchor = false;
		return $text;
	}

	/**
	 * Callback method to parse referenced anchors
	 * @param  string $matches
	 * @return string
	 */
	protected function _doAnchors_reference_callback($matches) {
		$whole_match =  $matches[1];
		$link_text   =  $matches[2];
		$link_id     =& $matches[3];

		if ($link_id == "") {
			// for shortcut links like [this][] or [this].
			$link_id = $link_text;
		}

		// lower-case and turn embedded newlines into spaces
		$link_id = strtolower($link_id);
		$link_id = preg_replace('{[ ]?\n}', ' ', $link_id);

		if (isset($this->urls[$link_id])) {
			$url = $this->urls[$link_id];
			$url = $this->encodeURLAttribute($url);

			$result = "titles[$link_id] ) ) {
				$title = $this->titles[$link_id];
				$title = $this->encodeAttribute($title);
				$result .=  " title=\"$title\"";
			}

			$link_text = $this->runSpanGamut($link_text);
			$result .= ">$link_text";
			$result = $this->hashPart($result);
		} else {
			$result = $whole_match;
		}
		return $result;
	}

	/**
	 * Callback method to parse inline anchors
	 * @param  string $matches
	 * @return string
	 */
	protected function _doAnchors_inline_callback($matches) {
		$whole_match	=  $matches[1];
		$link_text		=  $this->runSpanGamut($matches[2]);
		$url			=  $matches[3] == '' ? $matches[4] : $matches[3];
		$title			=& $matches[7];

		// If the URL was of the form <s p a c e s> it got caught by the HTML
		// tag parser and hashed. Need to reverse the process before using
		// the URL.
		$unhashed = $this->unhash($url);
		if ($unhashed != $url)
			$url = preg_replace('/^<(.*)>$/', '\1', $unhashed);

		$url = $this->encodeURLAttribute($url);

		$result = "encodeAttribute($title);
			$result .=  " title=\"$title\"";
		}

		$link_text = $this->runSpanGamut($link_text);
		$result .= ">$link_text";

		return $this->hashPart($result);
	}

	/**
	 * Turn Markdown image shortcuts into  tags.
	 * @param  string $text
	 * @return string
	 */
	protected function doImages($text) {
		// First, handle reference-style labeled images: ![alt text][id]
		$text = preg_replace_callback('{
			(				# wrap whole match in $1
			  !\[
				('.$this->nested_brackets_re.')		# alt text = $2
			  \]

			  [ ]?				# one optional space
			  (?:\n[ ]*)?		# one optional newline followed by spaces

			  \[
				(.*?)		# id = $3
			  \]

			)
			}xs',
			array($this, '_doImages_reference_callback'), $text);

		// Next, handle inline images:  ![alt text](url "optional title")
		// Don't forget: encode * and _
		$text = preg_replace_callback('{
			(				# wrap whole match in $1
			  !\[
				('.$this->nested_brackets_re.')		# alt text = $2
			  \]
			  \s?			# One optional whitespace character
			  \(			# literal paren
				[ \n]*
				(?:
					<(\S*)>	# src url = $3
				|
					('.$this->nested_url_parenthesis_re.')	# src url = $4
				)
				[ \n]*
				(			# $5
				  ([\'"])	# quote char = $6
				  (.*?)		# title = $7
				  \6		# matching quote
				  [ \n]*
				)?			# title is optional
			  \)
			)
			}xs',
			array($this, '_doImages_inline_callback'), $text);

		return $text;
	}

	/**
	 * Callback to parse references image tags
	 * @param  array $matches
	 * @return string
	 */
	protected function _doImages_reference_callback($matches) {
		$whole_match = $matches[1];
		$alt_text    = $matches[2];
		$link_id     = strtolower($matches[3]);

		if ($link_id == "") {
			$link_id = strtolower($alt_text); // for shortcut links like ![this][].
		}

		$alt_text = $this->encodeAttribute($alt_text);
		if (isset($this->urls[$link_id])) {
			$url = $this->encodeURLAttribute($this->urls[$link_id]);
			$result = "titles[$link_id])) {
				$title = $this->titles[$link_id];
				$title = $this->encodeAttribute($title);
				$result .=  " title=\"$title\"";
			}
			$result .= $this->empty_element_suffix;
			$result = $this->hashPart($result);
		} else {
			// If there's no such link ID, leave intact:
			$result = $whole_match;
		}

		return $result;
	}

	/**
	 * Callback to parse inline image tags
	 * @param  array $matches
	 * @return string
	 */
	protected function _doImages_inline_callback($matches) {
		$whole_match	= $matches[1];
		$alt_text		= $matches[2];
		$url			= $matches[3] == '' ? $matches[4] : $matches[3];
		$title			=& $matches[7];

		$alt_text = $this->encodeAttribute($alt_text);
		$url = $this->encodeURLAttribute($url);
		$result = "encodeAttribute($title);
			$result .=  " title=\"$title\""; // $title already quoted
		}
		$result .= $this->empty_element_suffix;

		return $this->hashPart($result);
	}

	/**
	 * Parse Markdown heading elements to HTML
	 * @param  string $text
	 * @return string
	 */
	protected function doHeaders($text) {
		/**
		 * Setext-style headers:
		 *	  Header 1
		 *	  ========
		 *
		 *	  Header 2
		 *	  --------
		 */
		$text = preg_replace_callback('{ ^(.+?)[ ]*\n(=+|-+)[ ]*\n+ }mx',
			array($this, '_doHeaders_callback_setext'), $text);

		/**
		 * atx-style headers:
		 *   # Header 1
		 *   ## Header 2
		 *   ## Header 2 with closing hashes ##
		 *   ...
		 *   ###### Header 6
		 */
		$text = preg_replace_callback('{
				^(\#{1,6})	# $1 = string of #\'s
				[ ]*
				(.+?)		# $2 = Header text
				[ ]*
				\#*			# optional closing #\'s (not counted)
				\n+
			}xm',
			array($this, '_doHeaders_callback_atx'), $text);

		return $text;
	}

	/**
	 * Setext header parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doHeaders_callback_setext($matches) {
		// Terrible hack to check we haven't found an empty list item.
		if ($matches[2] == '-' && preg_match('{^-(?: |$)}', $matches[1])) {
			return $matches[0];
		}

		$level = $matches[2]{0} == '=' ? 1 : 2;

		// ID attribute generation
		$idAtt = $this->_generateIdFromHeaderValue($matches[1]);

		$block = "<h$level$idAtt>".$this->runSpanGamut($matches[1])."</h$level>";
		return "\n" . $this->hashBlock($block) . "\n\n";
	}

	/**
	 * ATX header parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doHeaders_callback_atx($matches) {
		// ID attribute generation
		$idAtt = $this->_generateIdFromHeaderValue($matches[2]);

		$level = strlen($matches[1]);
		$block = "<h$level$idAtt>".$this->runSpanGamut($matches[2])."</h$level>";
		return "\n" . $this->hashBlock($block) . "\n\n";
	}

	/**
	 * If a header_id_func property is set, we can use it to automatically
	 * generate an id attribute.
	 *
	 * This method returns a string in the form id="foo", or an empty string
	 * otherwise.
	 * @param  string $headerValue
	 * @return string
	 */
	protected function _generateIdFromHeaderValue($headerValue) {
		if (!is_callable($this->header_id_func)) {
			return "";
		}

		$idValue = call_user_func($this->header_id_func, $headerValue);
		if (!$idValue) {
			return "";
		}

		return ' id="' . $this->encodeAttribute($idValue) . '"';
	}

	/**
	 * Form HTML ordered (numbered) and unordered (bulleted) lists.
	 * @param  string $text
	 * @return string
	 */
	protected function doLists($text) {
		$less_than_tab = $this->tab_width - 1;

		// Re-usable patterns to match list item bullets and number markers:
		$marker_ul_re  = '[*+-]';
		$marker_ol_re  = '\d+[\.]';

		$markers_relist = array(
			$marker_ul_re => $marker_ol_re,
			$marker_ol_re => $marker_ul_re,
			);

		foreach ($markers_relist as $marker_re => $other_marker_re) {
			// Re-usable pattern to match any entirel ul or ol list:
			$whole_list_re = '
				(								# $1 = whole list
				  (								# $2
					([ ]{0,'.$less_than_tab.'})	# $3 = number of spaces
					('.$marker_re.')			# $4 = first list item marker
					[ ]+
				  )
				  (?s:.+?)
				  (								# $5
					  \z
					|
					  \n{2,}
					  (?=\S)
					  (?!						# Negative lookahead for another list item marker
						[ ]*
						'.$marker_re.'[ ]+
					  )
					|
					  (?=						# Lookahead for another kind of list
					    \n
						\3						# Must have the same indentation
						'.$other_marker_re.'[ ]+
					  )
				  )
				)
			'; // mx

			// We use a different prefix before nested lists than top-level lists.
			//See extended comment in _ProcessListItems().

			if ($this->list_level) {
				$text = preg_replace_callback('{
						^
						'.$whole_list_re.'
					}mx',
					array($this, '_doLists_callback'), $text);
			} else {
				$text = preg_replace_callback('{
						(?:(?<=\n)\n|\A\n?) # Must eat the newline
						'.$whole_list_re.'
					}mx',
					array($this, '_doLists_callback'), $text);
			}
		}

		return $text;
	}

	/**
	 * List parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doLists_callback($matches) {
		// Re-usable patterns to match list item bullets and number markers:
		$marker_ul_re  = '[*+-]';
		$marker_ol_re  = '\d+[\.]';
		$marker_any_re = "(?:$marker_ul_re|$marker_ol_re)";
		$marker_ol_start_re = '[0-9]+';

		$list = $matches[1];
		$list_type = preg_match("/$marker_ul_re/", $matches[4]) ? "ul" : "ol";

		$marker_any_re = ( $list_type == "ul" ? $marker_ul_re : $marker_ol_re );

		$list .= "\n";
		$result = $this->processListItems($list, $marker_any_re);

		$ol_start = 1;
		if ($this->enhanced_ordered_list) {
			// Get the start number for ordered list.
			if ($list_type == 'ol') {
				$ol_start_array = array();
				$ol_start_check = preg_match("/$marker_ol_start_re/", $matches[4], $ol_start_array);
				if ($ol_start_check){
					$ol_start = $ol_start_array[0];
				}
			}
		}

		if ($ol_start > 1 && $list_type == 'ol'){
			$result = $this->hashBlock("<$list_type start=\"$ol_start\">\n" . $result . "</$list_type>");
		} else {
			$result = $this->hashBlock("<$list_type>\n" . $result . "</$list_type>");
		}
		return "\n". $result ."\n\n";
	}

	/**
	 * Nesting tracker for list levels
	 * @var integer
	 */
	protected $list_level = 0;

	/**
	 * Process the contents of a single ordered or unordered list, splitting it
	 * into individual list items.
	 * @param  string $list_str
	 * @param  string $marker_any_re
	 * @return string
	 */
	protected function processListItems($list_str, $marker_any_re) {
		/**
		 * The $this->list_level global keeps track of when we're inside a list.
		 * Each time we enter a list, we increment it; when we leave a list,
		 * we decrement. If it's zero, we're not in a list anymore.
		 *
		 * We do this because when we're not inside a list, we want to treat
		 * something like this:
		 *
		 *		I recommend upgrading to version
		 *		8. Oops, now this line is treated
		 *		as a sub-list.
		 *
		 * As a single paragraph, despite the fact that the second line starts
		 * with a digit-period-space sequence.
		 *
		 * Whereas when we're inside a list (or sub-list), that line will be
		 * treated as the start of a sub-list. What a kludge, huh? This is
		 * an aspect of Markdown's syntax that's hard to parse perfectly
		 * without resorting to mind-reading. Perhaps the solution is to
		 * change the syntax rules such that sub-lists must start with a
		 * starting cardinal number; e.g. "1." or "a.".
		 */
		$this->list_level++;

		// Trim trailing blank lines:
		$list_str = preg_replace("/\n{2,}\\z/", "\n", $list_str);

		$list_str = preg_replace_callback('{
			(\n)?							# leading line = $1
			(^[ ]*)							# leading whitespace = $2
			('.$marker_any_re.'				# list marker and space = $3
				(?:[ ]+|(?=\n))	# space only required if item is not empty
			)
			((?s:.*?))						# list item text   = $4
			(?:(\n+(?=\n))|\n)				# tailing blank line = $5
			(?= \n* (\z | \2 ('.$marker_any_re.') (?:[ ]+|(?=\n))))
			}xm',
			array($this, '_processListItems_callback'), $list_str);

		$this->list_level--;
		return $list_str;
	}

	/**
	 * List item parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _processListItems_callback($matches) {
		$item = $matches[4];
		$leading_line =& $matches[1];
		$leading_space =& $matches[2];
		$marker_space = $matches[3];
		$tailing_blank_line =& $matches[5];

		if ($leading_line || $tailing_blank_line ||
			preg_match('/\n{2,}/', $item))
		{
			// Replace marker with the appropriate whitespace indentation
			$item = $leading_space . str_repeat(' ', strlen($marker_space)) . $item;
			$item = $this->runBlockGamut($this->outdent($item)."\n");
		} else {
			// Recursion for sub-lists:
			$item = $this->doLists($this->outdent($item));
			$item = $this->formParagraphs($item, false);
		}

		return "
" . $item . "
\n";
	}

	/**
	 * Process Markdown `<pre><code>` blocks.
	 * @param  string $text
	 * @return string
	 */
	protected function doCodeBlocks($text) {
		$text = preg_replace_callback('{
				(?:\n\n|\A\n?)
				(	            # $1 = the code block -- one or more lines, starting with a space/tab
				  (?>
					[ ]{'.$this->tab_width.'}  # Lines must start with a tab or a tab-width of spaces
					.*\n+
				  )+
				)
				((?=^[ ]{0,'.$this->tab_width.'}\S)|\Z)	# Lookahead for non-space at line-start, or end of doc
			}xm',
			array($this, '_doCodeBlocks_callback'), $text);

		return $text;
	}

	/**
	 * Code block parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doCodeBlocks_callback($matches) {
		$codeblock = $matches[1];

		$codeblock = $this->outdent($codeblock);
		if ($this->code_block_content_func) {
			$codeblock = call_user_func($this->code_block_content_func, $codeblock, "");
		} else {
			$codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES);
		}

		# trim leading newlines and trailing newlines
		$codeblock = preg_replace('/\A\n+|\n+\z/', '', $codeblock);

		$codeblock = "
$codeblock\n
";
		return "\n\n" . $this->hashBlock($codeblock) . "\n\n";
	}

	/**
	 * Create a code span markup for $code. Called from handleSpanToken.
	 * @param  string $code
	 * @return string
	 */
	protected function makeCodeSpan($code) {
		if ($this->code_span_content_func) {
			$code = call_user_func($this->code_span_content_func, $code);
		} else {
			$code = htmlspecialchars(trim($code), ENT_NOQUOTES);
		}
		return $this->hashPart("$code");
	}

	/**
	 * Define the emphasis operators with their regex matches
	 * @var array
	 */
	protected $em_relist = array(
		''  => '(?:(?<!\*)\*(?!\*)|(?<!_)_(?!_))(?![\.,:;]?\s)',
		'*' => '(?<![\s*])\*(?!\*)',
		'_' => '(?<![\s_])_(?!_)',
	);

	/**
	 * Define the strong operators with their regex matches
	 * @var array
	 */
	protected $strong_relist = array(
		''   => '(?:(?<!\*)\*\*(?!\*)|(?<!_)__(?!_))(?![\.,:;]?\s)',
		'**' => '(?<![\s*])\*\*(?!\*)',
		'__' => '(?<![\s_])__(?!_)',
	);

	/**
	 * Define the emphasis + strong operators with their regex matches
	 * @var array
	 */
	protected $em_strong_relist = array(
		''    => '(?:(?<!\*)\*\*\*(?!\*)|(?<!_)___(?!_))(?![\.,:;]?\s)',
		'***' => '(?<![\s*])\*\*\*(?!\*)',
		'___' => '(?<![\s_])___(?!_)',
	);

	/**
	 * Container for prepared regular expressions
	 * @var array
	 */
	protected $em_strong_prepared_relist;

	/**
	 * Prepare regular expressions for searching emphasis tokens in any
	 * context.
	 * @return void
	 */
	protected function prepareItalicsAndBold() {
		foreach ($this->em_relist as $em => $em_re) {
			foreach ($this->strong_relist as $strong => $strong_re) {
				// Construct list of allowed token expressions.
				$token_relist = array();
				if (isset($this->em_strong_relist["$em$strong"])) {
					$token_relist[] = $this->em_strong_relist["$em$strong"];
				}
				$token_relist[] = $em_re;
				$token_relist[] = $strong_re;

				// Construct master expression from list.
				$token_re = '{(' . implode('|', $token_relist) . ')}';
				$this->em_strong_prepared_relist["$em$strong"] = $token_re;
			}
		}
	}

	/**
	 * Convert Markdown italics (emphasis) and bold (strong) to HTML
	 * @param  string $text
	 * @return string
	 */
	protected function doItalicsAndBold($text) {
		if ($this->in_emphasis_processing) {
			return $text; // avoid reentrency
		}
		$this->in_emphasis_processing = true;

		$token_stack = array('');
		$text_stack = array('');
		$em = '';
		$strong = '';
		$tree_char_em = false;

		while (1) {
			// Get prepared regular expression for seraching emphasis tokens
			// in current context.
			$token_re = $this->em_strong_prepared_relist["$em$strong"];

			// Each loop iteration search for the next emphasis token.
			// Each token is then passed to handleSpanToken.
			$parts = preg_split($token_re, $text, 2, PREG_SPLIT_DELIM_CAPTURE);
			$text_stack[0] .= $parts[0];
			$token =& $parts[1];
			$text =& $parts[2];

			if (empty($token)) {
				// Reached end of text span: empty stack without emitting.
				// any more emphasis.
				while ($token_stack[0]) {
					$text_stack[1] .= array_shift($token_stack);
					$text_stack[0] .= array_shift($text_stack);
				}
				break;
			}

			$token_len = strlen($token);
			if ($tree_char_em) {
				// Reached closing marker while inside a three-char emphasis.
				if ($token_len == 3) {
					// Three-char closing marker, close em and strong.
					array_shift($token_stack);
					$span = array_shift($text_stack);
					$span = $this->runSpanGamut($span);
					$span = "$span";
					$text_stack[0] .= $this->hashPart($span);
					$em = '';
					$strong = '';
				} else {
					// Other closing marker: close one em or strong and
					// change current token state to match the other
					$token_stack[0] = str_repeat($token{0}, 3-$token_len);
					$tag = $token_len == 2 ? "strong" : "em";
					$span = $text_stack[0];
					$span = $this->runSpanGamut($span);
					$span = "<$tag>$span</$tag>";
					$text_stack[0] = $this->hashPart($span);
					$$tag = ''; // $$tag stands for $em or $strong
				}
				$tree_char_em = false;
			} else if ($token_len == 3) {
				if ($em) {
					// Reached closing marker for both em and strong.
					// Closing strong marker:
					for ($i = 0; $i < 2; ++$i) {
						$shifted_token = array_shift($token_stack);
						$tag = strlen($shifted_token) == 2 ? "strong" : "em";
						$span = array_shift($text_stack);
						$span = $this->runSpanGamut($span);
						$span = "<$tag>$span</$tag>";
						$text_stack[0] .= $this->hashPart($span);
						$$tag = ''; // $$tag stands for $em or $strong
					}
				} else {
					// Reached opening three-char emphasis marker. Push on token
					// stack; will be handled by the special condition above.
					$em = $token{0};
					$strong = "$em$em";
					array_unshift($token_stack, $token);
					array_unshift($text_stack, '');
					$tree_char_em = true;
				}
			} else if ($token_len == 2) {
				if ($strong) {
					// Unwind any dangling emphasis marker:
					if (strlen($token_stack[0]) == 1) {
						$text_stack[1] .= array_shift($token_stack);
						$text_stack[0] .= array_shift($text_stack);
						$em = '';
					}
					// Closing strong marker:
					array_shift($token_stack);
					$span = array_shift($text_stack);
					$span = $this->runSpanGamut($span);
					$span = "$span";
					$text_stack[0] .= $this->hashPart($span);
					$strong = '';
				} else {
					array_unshift($token_stack, $token);
					array_unshift($text_stack, '');
					$strong = $token;
				}
			} else {
				// Here $token_len == 1
				if ($em) {
					if (strlen($token_stack[0]) == 1) {
						// Closing emphasis marker:
						array_shift($token_stack);
						$span = array_shift($text_stack);
						$span = $this->runSpanGamut($span);
						$span = "$span";
						$text_stack[0] .= $this->hashPart($span);
						$em = '';
					} else {
						$text_stack[0] .= $token;
					}
				} else {
					array_unshift($token_stack, $token);
					array_unshift($text_stack, '');
					$em = $token;
				}
			}
		}
		$this->in_emphasis_processing = false;
		return $text_stack[0];
	}

	/**
	 * Parse Markdown blockquotes to HTML
	 * @param  string $text
	 * @return string
	 */
	protected function doBlockQuotes($text) {
		$text = preg_replace_callback('/
			  (								# Wrap whole match in $1
				(?>
				  ^[ ]*>[ ]?			# ">" at the start of a line
					.+\n					# rest of the first line
				  (.+\n)*					# subsequent consecutive lines
				  \n*						# blanks
				)+
			  )
			/xm',
			array($this, '_doBlockQuotes_callback'), $text);

		return $text;
	}

	/**
	 * Blockquote parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doBlockQuotes_callback($matches) {
		$bq = $matches[1];
		// trim one level of quoting - trim whitespace-only lines
		$bq = preg_replace('/^[ ]*>[ ]?|^[ ]+$/m', '', $bq);
		$bq = $this->runBlockGamut($bq); // recurse

		$bq = preg_replace('/^/m', "  ", $bq);
		// These leading spaces cause problem with
 content,
		// so we need to fix that:
		$bq = preg_replace_callback('{(\s*
.+?
)}sx',
			array($this, '_doBlockQuotes_callback2'), $bq);

		return "\n" . $this->hashBlock("
\n$bq\n
") . "\n\n";
	}

	/**
	 * Blockquote parsing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doBlockQuotes_callback2($matches) {
		$pre = $matches[1];
		$pre = preg_replace('/^  /m', '', $pre);
		return $pre;
	}

	/**
	 * Parse paragraphs
	 *
	 * @param  string $text String to process in paragraphs
	 * @param  boolean $wrap_in_p Whether paragraphs should be wrapped in
 tags
	 * @return string
	 */
	protected function formParagraphs($text, $wrap_in_p = true) {
		// Strip leading and trailing lines:
		$text = preg_replace('/\A\n+|\n+\z/', '', $text);

		$grafs = preg_split('/\n{2,}/', $text, -1, PREG_SPLIT_NO_EMPTY);

		// Wrap
 tags and unhashify HTML blocks
		foreach ($grafs as $key => $value) {
			if (!preg_match('/^B\x1A[0-9]+B$/', $value)) {
				// Is a paragraph.
				$value = $this->runSpanGamut($value);
				if ($wrap_in_p) {
					$value = preg_replace('/^([ ]*)/', "
", $value);
					$value .= "
";
				}
				$grafs[$key] = $this->unhash($value);
			} else {
				// Is a block.
				// Modify elements of @grafs in-place...
				$graf = $value;
				$block = $this->html_hashes[$graf];
				$graf = $block;
//				if (preg_match('{
//					\A
//					(							# $1 =
 tag
//
]*
//					  \b
//					  markdown\s*=\s*  ([\'"])	#	$2 = attr quote char
//					  1
//					  \2
//					  [^>]*
//					  >
//					)
//					(							# $3 = contents
//					.*
//					)
//					(
)					# $4 = closing tag
//					\z
//					}xs', $block, $matches))
//				{
//					list(, $div_open, , $div_content, $div_close) = $matches;
//
//					// We can't call Markdown(), because that resets the hash;
//					// that initialization code should be pulled into its own sub, though.
//					$div_content = $this->hashHTMLBlocks($div_content);
//
//					// Run document gamut methods on the content.
//					foreach ($this->document_gamut as $method => $priority) {
//						$div_content = $this->$method($div_content);
//					}
//
//					$div_open = preg_replace(
//						'{\smarkdown\s*=\s*([\'"]).+?\1}', '', $div_open);
//
//					$graf = $div_open . "\n" . $div_content . "\n" . $div_close;
//				}
				$grafs[$key] = $graf;
			}
		}

		return implode("\n\n", $grafs);
	}

	/**
	 * Encode text for a double-quoted HTML attribute. This function
	 * is *not* suitable for attributes enclosed in single quotes.
	 * @param  string $text
	 * @return string
	 */
	protected function encodeAttribute($text) {
		$text = $this->encodeAmpsAndAngles($text);
		$text = str_replace('"', '"', $text);
		return $text;
	}

	/**
	 * Encode text for a double-quoted HTML attribute containing a URL,
	 * applying the URL filter if set. Also generates the textual
	 * representation for the URL (removing mailto: or tel:) storing it in $text.
	 * This function is *not* suitable for attributes enclosed in single quotes.
	 *
	 * @param  string $url
	 * @param  string &$text Passed by reference
	 * @return string        URL
	 */
	protected function encodeURLAttribute($url, &$text = null) {
		if ($this->url_filter_func) {
			$url = call_user_func($this->url_filter_func, $url);
		}

		if (preg_match('{^mailto:}i', $url)) {
			$url = $this->encodeEntityObfuscatedAttribute($url, $text, 7);
		} else if (preg_match('{^tel:}i', $url)) {
			$url = $this->encodeAttribute($url);
			$text = substr($url, 4);
		} else {
			$url = $this->encodeAttribute($url);
			$text = $url;
		}

		return $url;
	}

	/**
	 * Smart processing for ampersands and angle brackets that need to
	 * be encoded. Valid character entities are left alone unless the
	 * no-entities mode is set.
	 * @param  string $text
	 * @return string
	 */
	protected function encodeAmpsAndAngles($text) {
		if ($this->no_entities) {
			$text = str_replace('&', '&', $text);
		} else {
			// Ampersand-encoding based entirely on Nat Irons's Amputator
			// MT plugin: <http://bumppo.net/projects/amputator/>
			$text = preg_replace('/&(?!#?[xX]?(?:[0-9a-fA-F]+|\w+);)/',
								'&', $text);
		}
		// Encode remaining <'s
		$text = str_replace('<', '<', $text);

		return $text;
	}

	/**
	 * Parse Markdown automatic links to anchor HTML tags
	 * @param  string $text
	 * @return string
	 */
	protected function doAutoLinks($text) {
		$text = preg_replace_callback('{<((https?|ftp|dict|tel):[^\'">\s]+)>}i',
			array($this, '_doAutoLinks_url_callback'), $text);

		// Email addresses: <address@domain.foo>
		$text = preg_replace_callback('{
			<
			(?:mailto:)?
			(
				(?:
					[-!#$%&\'*+/=?^_`.{|}~\w\x80-\xFF]+
				|
					".*?"
				)
				\@
				(?:
					[-a-z0-9\x80-\xFF]+(\.[-a-z0-9\x80-\xFF]+)*\.[a-z]+
				|
					\[[\d.a-fA-F:]+\]	# IPv4 & IPv6
				)
			)
			>
			}xi',
			array($this, '_doAutoLinks_email_callback'), $text);

		return $text;
	}

	/**
	 * Parse URL callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doAutoLinks_url_callback($matches) {
		$url = $this->encodeURLAttribute($matches[1], $text);
		$link = "$text";
		return $this->hashPart($link);
	}

	/**
	 * Parse email address callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _doAutoLinks_email_callback($matches) {
		$addr = $matches[1];
		$url = $this->encodeURLAttribute("mailto:$addr", $text);
		$link = "$text";
		return $this->hashPart($link);
	}

	/**
	 * Input: some text to obfuscate, e.g. "mailto:foo@example.com"
	 *
	 * Output: the same text but with most characters encoded as either a
	 *         decimal or hex entity, in the hopes of foiling most address
	 *         harvesting spam bots. E.g.:
	 *
	 *        mailto:foo
	 *        @example.co
	 *        m
	 *
	 * Note: the additional output $tail is assigned the same value as the
	 * ouput, minus the number of characters specified by $head_length.
	 *
	 * Based by a filter by Matthew Wickline, posted to BBEdit-Talk.
	 * With some optimizations by Milian Wolff. Forced encoding of HTML
	 * attribute special characters by Allan Odgaard.
	 *
	 * @param  string  $text
	 * @param  string  &$tail
	 * @param  integer $head_length
	 * @return string
	 */
	protected function encodeEntityObfuscatedAttribute($text, &$tail = null, $head_length = 0) {
		if ($text == "") {
			return $tail = "";
		}

		$chars = preg_split('/(?<!^)(?!$)/', $text);
		$seed = (int)abs(crc32($text) / strlen($text)); // Deterministic seed.

		foreach ($chars as $key => $char) {
			$ord = ord($char);
			// Ignore non-ascii chars.
			if ($ord < 128) {
				$r = ($seed * (1 + $key)) % 100; // Pseudo-random function.
				// roughly 10% raw, 45% hex, 45% dec
				// '@' *must* be encoded. I insist.
				// '"' and '>' have to be encoded inside the attribute
				if ($r > 90 && strpos('@"&>', $char) === false) {
					/* do nothing */
				} else if ($r < 45) {
					$chars[$key] = '&#x'.dechex($ord).';';
				} else {
					$chars[$key] = '&#'.$ord.';';
				}
			}
		}

		$text = implode('', $chars);
		$tail = $head_length ? implode('', array_slice($chars, $head_length)) : $text;

		return $text;
	}

	/**
	 * Take the string $str and parse it into tokens, hashing embeded HTML,
	 * escaped characters and handling code spans.
	 * @param  string $str
	 * @return string
	 */
	protected function parseSpan($str) {
		$output = '';

		$span_re = '{
				(
					\\\\'.$this->escape_chars_re.'
				|
					(?<![`\\\\])
					`+						# code span marker
			'.( $this->no_markup ? '' : '
				|
							# comment
				|
					<\?.*?\?> | <%.*?%>		# processing instruction
				|
					<[!$]?[-a-zA-Z0-9:_]+	# regular tags
					(?>
						\s
						(?>[^"\'>]+|"[^"]*"|\'[^\']*\')*
					)?
					>
				|
					<[-a-zA-Z0-9:_]+\s*/> # xml-style empty tag
				|
					</[-a-zA-Z0-9:_]+\s*> # closing tag
			').'
				)
				}xs';

		while (1) {
			// Each loop iteration seach for either the next tag, the next
			// openning code span marker, or the next escaped character.
			// Each token is then passed to handleSpanToken.
			$parts = preg_split($span_re, $str, 2, PREG_SPLIT_DELIM_CAPTURE);

			// Create token from text preceding tag.
			if ($parts[0] != "") {
				$output .= $parts[0];
			}

			// Check if we reach the end.
			if (isset($parts[1])) {
				$output .= $this->handleSpanToken($parts[1], $parts[2]);
				$str = $parts[2];
			} else {
				break;
			}
		}

		return $output;
	}

	/**
	 * Handle $token provided by parseSpan by determining its nature and
	 * returning the corresponding value that should replace it.
	 * @param  string $token
	 * @param  string &$str
	 * @return string
	 */
	protected function handleSpanToken($token, &$str) {
		switch ($token{0}) {
			case "\\":
				return $this->hashPart("&#". ord($token{1}). ";");
			case "`":
				// Search for end marker in remaining text.
				if (preg_match('/^(.*?[^`])'.preg_quote($token).'(?!`)(.*)$/sm',
					$str, $matches))
				{
					$str = $matches[2];
					$codespan = $this->makeCodeSpan($matches[1]);
					return $this->hashPart($codespan);
				}
				return $token; // Return as text since no ending marker found.
			default:
				return $this->hashPart($token);
		}
	}

	/**
	 * Remove one level of line-leading tabs or spaces
	 * @param  string $text
	 * @return string
	 */
	protected function outdent($text) {
		return preg_replace('/^(\t|[ ]{1,' . $this->tab_width . '})/m', '', $text);
	}

	/**
	 * String length function for detab. `_initDetab` will create a function to
	 * handle UTF-8 if the default function does not exist.
	 * @var string
	 */
	protected $utf8_strlen = 'mb_strlen';

	/**
	 * Replace tabs with the appropriate amount of spaces.
	 *
	 * For each line we separate the line in blocks delemited by tab characters.
	 * Then we reconstruct every line by adding the  appropriate number of space
	 * between each blocks.
	 *
	 * @param  string $text
	 * @return string
	 */
	protected function detab($text) {
		$text = preg_replace_callback('/^.*\t.*$/m',
			array($this, '_detab_callback'), $text);

		return $text;
	}

	/**
	 * Replace tabs callback
	 * @param  string $matches
	 * @return string
	 */
	protected function _detab_callback($matches) {
		$line = $matches[0];
		$strlen = $this->utf8_strlen; // strlen function for UTF-8.

		// Split in blocks.
		$blocks = explode("\t", $line);
		// Add each blocks to the line.
		$line = $blocks[0];
		unset($blocks[0]); // Do not add first block twice.
		foreach ($blocks as $block) {
			// Calculate amount of space, insert spaces, insert block.
			$amount = $this->tab_width -
				$strlen($line, 'UTF-8') % $this->tab_width;
			$line .= str_repeat(" ", $amount) . $block;
		}
		return $line;
	}

	/**
	 * Check for the availability of the function in the `utf8_strlen` property
	 * (initially `mb_strlen`). If the function is not available, create a
	 * function that will loosely count the number of UTF-8 characters with a
	 * regular expression.
	 * @return void
	 */
	protected function _initDetab() {

		if (function_exists($this->utf8_strlen)) {
			return;
		}

		$this->utf8_strlen = function($text) {
			return preg_match_all('/[\x00-\xBF]|[\xC0-\xFF][\x80-\xBF]*/', $text, $m);
		};
	}

	/**
	 * Swap back in all the tags hashed by _HashHTMLBlocks.
	 * @param  string $text
	 * @return string
	 */
	protected function unhash($text) {
		return preg_replace_callback('/(.)\x1A[0-9]+\1/',
			array($this, '_unhash_callback'), $text);
	}

	/**
	 * Unhashing callback
	 * @param  array $matches
	 * @return string
	 */
	protected function _unhash_callback($matches) {
		return $this->html_hashes[$matches[0]];
	}
}


code:
<?php

// Use this file if you cannot use class autoloading. It will include all the
// files needed for the MarkdownExtra parser.
//
// Take a look at the PSR-0-compatible class autoloading implementation
// in the Readme.php file if you want a simple autoloader setup.

require_once dirname(__FILE__) . '/MarkdownInterface.php';
require_once dirname(__FILE__) . '/Markdown.php';
require_once dirname(__FILE__) . '/MarkdownExtra.php';