balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

parsedown.php (41511B)


      1 <?php
      2 
      3 #
      4 #
      5 # Parsedown
      6 # http://parsedown.org
      7 #
      8 # (c) Emanuil Rusev
      9 # http://erusev.com
     10 #
     11 # For the full license information, view the LICENSE file that was distributed
     12 # with this source code.
     13 #
     14 #
     15 
     16 if (! class_exists( 'Redux_Parsedown' ) ) {
     17 	class Redux_Parsedown
     18 	{
     19 		# ~
     20 
     21 		const version = '1.8.0-beta-7';
     22 
     23 		# ~
     24 
     25 		function text($text)
     26 		{
     27 			$Elements = $this->textElements($text);
     28 
     29 			# convert to markup
     30 			$markup = $this->elements($Elements);
     31 
     32 			# trim line breaks
     33 			$markup = trim($markup, "\n");
     34 
     35 			return $markup;
     36 		}
     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 			# ~
    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 		);
   1122 
   1123 		# ~
   1124 
   1125 		protected $inlineMarkerList = '!*_&[:<`~\\';
   1126 
   1127 		#
   1128 		# ~
   1129 		#
   1130 
   1131 		public function line($text, $nonNestables = array())
   1132 		{
   1133 			return $this->elements($this->lineElements($text, $nonNestables));
   1134 		}
   1135 
   1136 		protected function lineElements($text, $nonNestables = array())
   1137 		{
   1138 			# standardize line breaks
   1139 			$text = str_replace(array("\r\n", "\r"), "\n", $text);
   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);
   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
   1189 
   1190 
   1191 					$Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
   1192 						? array_merge($Inline['element']['nonNestables'], $nonNestables)
   1193 						: $nonNestables
   1194 					;
   1195 
   1196 					# the text that comes before the inline
   1197 					$unmarkedText = substr($text, 0, $Inline['position']);
   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 	}
   1996 }