aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--composer.json3
-rw-r--r--composer.lock58
-rw-r--r--include/markdown.php10
-rw-r--r--library/markdownify/TODO29
-rw-r--r--library/markdownify/example.php51
-rw-r--r--library/markdownify/markdownify.php1197
-rwxr-xr-xlibrary/markdownify/markdownify_cli.php33
-rw-r--r--library/markdownify/markdownify_extra.php489
-rw-r--r--library/markdownify/parsehtml/parsehtml.php618
-rw-r--r--vendor/composer/autoload_classmap.php9
-rw-r--r--vendor/composer/autoload_psr4.php2
-rw-r--r--vendor/composer/autoload_static.php25
-rw-r--r--vendor/composer/installed.json58
-rw-r--r--vendor/pixel418/markdownify/CHANGELOG.md76
-rw-r--r--vendor/pixel418/markdownify/LICENSE (renamed from library/markdownify/LICENSE_LGPL.txt)0
-rw-r--r--vendor/pixel418/markdownify/README.md63
-rw-r--r--vendor/pixel418/markdownify/composer.json38
-rw-r--r--vendor/pixel418/markdownify/src/Converter.php1400
-rw-r--r--vendor/pixel418/markdownify/src/ConverterExtra.php573
-rw-r--r--vendor/pixel418/markdownify/src/Parser.php564
20 files changed, 2869 insertions, 2427 deletions
diff --git a/composer.json b/composer.json
index d977987eb..b59c81bba 100644
--- a/composer.json
+++ b/composer.json
@@ -29,7 +29,8 @@
"ext-xml" : "*",
"ext-openssl" : "*",
"sabre/dav" : "~3.2",
- "michelf/php-markdown" : "^1.7"
+ "michelf/php-markdown" : "^1.7",
+ "pixel418/markdownify": "^2.2"
},
"require-dev" : {
"php" : ">=5.6",
diff --git a/composer.lock b/composer.lock
index cf348bdae..06fb17410 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"This file is @generated automatically"
],
- "content-hash": "4b24468c1f8babe7c8804fba8ee602f7",
+ "content-hash": "c0cafbf9fd702be588f6b392b9742cb6",
"packages": [
{
"name": "michelf/php-markdown",
@@ -58,6 +58,62 @@
"time": "2016-10-29T18:58:20+00:00"
},
{
+ "name": "pixel418/markdownify",
+ "version": "v2.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Elephant418/Markdownify.git",
+ "reference": "0160677f04c784550dd10fd72fdf3994967db848"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Elephant418/Markdownify/zipball/0160677f04c784550dd10fd72fdf3994967db848",
+ "reference": "0160677f04c784550dd10fd72fdf3994967db848",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8"
+ },
+ "type": "lib",
+ "autoload": {
+ "psr-4": {
+ "Markdownify\\": "src",
+ "Test\\Markdownify\\": "test"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL"
+ ],
+ "authors": [
+ {
+ "name": "Peter Kruithof",
+ "email": "pkruithof@gmail.com",
+ "homepage": "http://pkruithof.tumblr.com/"
+ },
+ {
+ "name": "Milian Wolff",
+ "email": "mail@milianw.de",
+ "homepage": "http://milianw.de"
+ },
+ {
+ "name": "Thomas Zilliox",
+ "email": "hello@tzi.fr",
+ "homepage": "http://tzi.fr"
+ }
+ ],
+ "description": "The HTML to Markdown converter for PHP ",
+ "homepage": "https://github.com/elephant418/Markdownify",
+ "keywords": [
+ "markdown",
+ "markdownify"
+ ],
+ "time": "2016-09-21T13:01:43+00:00"
+ },
+ {
"name": "psr/log",
"version": "1.0.2",
"source": {
diff --git a/include/markdown.php b/include/markdown.php
index 43d973808..7e1f1c42c 100644
--- a/include/markdown.php
+++ b/include/markdown.php
@@ -5,12 +5,12 @@
*/
use Michelf\MarkdownExtra;
+use Markdownify\Converter;
require_once("include/oembed.php");
require_once("include/event.php");
require_once("include/html2bbcode.php");
require_once("include/bbcode.php");
-require_once("library/markdownify/markdownify.php");
function get_bb_tag_pos($s, $name, $occurance = 1) {
@@ -367,7 +367,6 @@ function bb2diaspora_itemwallwall(&$item,$uplink = false) {
function bb2diaspora_itembody($item, $force_update = false, $have_channel = false, $uplink = false) {
-
if(! get_iconfig($item,'diaspora','fields')) {
$force_update = true;
}
@@ -454,7 +453,7 @@ function bb2diaspora_itembody($item, $force_update = false, $have_channel = fals
return html_entity_decode($body);
}
-function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) {
+function bb2diaspora($Text, $preserve_nl = false, $fordiaspora = true) {
// Re-enabling the converter again.
// The bbcode parser now handles youtube-links (and the other stuff) correctly.
@@ -496,11 +495,10 @@ function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) {
$Text = str_replace(array('<','>','&'),array('&_lt_;','&_gt_;','&_amp_;'),$Text);
// Now convert HTML to Markdown
- $md = new Markdownify(false, false, false);
+ $md = new Converter(Converter::LINK_AFTER_CONTENT, false, false);
$Text = $md->parseString($Text);
-
// It also adds backslashes to our attempt at getting around the html entity preservation for some weird reason.
$Text = str_replace(array('&\\_lt\\_;','&\\_gt\\_;','&\\_amp\\_;'),array('<','>','&'),$Text);
@@ -522,7 +520,7 @@ function bb2diaspora($Text,$preserve_nl = false, $fordiaspora = true) {
$Text = trim($Text);
- call_hooks('bb2diaspora',$Text);
+ call_hooks('bb2diaspora', $Text);
return $Text;
}
diff --git a/library/markdownify/TODO b/library/markdownify/TODO
deleted file mode 100644
index 06ec8508b..000000000
--- a/library/markdownify/TODO
+++ /dev/null
@@ -1,29 +0,0 @@
-Markdownify
-===========
-* handle non-markdownifiable lists (i.e. `<ul><li id="foobar">asdf</li></ul>`)
-* organize methods better (i.e. flushlinebreaks & setlinebreaks close to each other)
-* take a look at function names etc.
-* is the new (in rev. 93) lastclosedtag property needed?
-* word wrapping (some work is done but it's still very buggy)
-
-
-Markdownify Extra
-=================
-
-* handle table alignment with KEEP_HTML=false
-* handle tables without headings when KEEP_HTML=false is set
-* handle Markdown inside non-markdownable tags
-
-
-Implementation Thoughts
-=======================
-* non-markdownifiable lists and markdown inside non-markdownable tags as well as the current
- table implementation could be rewritten by using a rollback mechanism.
-
- example:
-
- <ul><li>asdf</li><li id="foobar">asdf</li></ul>
-
- we come to `<ul>`, know that this might fail and create a snapshot of our current parser
- we keep on parsing and when we reach `<li id="foobar">` we gotta rollback and keep this
- list in HTML format.
diff --git a/library/markdownify/example.php b/library/markdownify/example.php
deleted file mode 100644
index ef86dca83..000000000
--- a/library/markdownify/example.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
- error_reporting(E_ALL);
- if (!empty($_POST['input'])) {
- include 'markdownify_extra.php';
- if (!isset($_POST['leap'])) {
- $leap = MDFY_LINKS_EACH_PARAGRAPH;
- } else {
- $leap = $_POST['leap'];
- }
-
- if (!isset($_POST['keepHTML'])) {
- $keephtml = MDFY_KEEPHTML;
- } else {
- $keephtml = $_POST['keepHTML'];
- }
- if (!empty($_POST['extra'])) {
- $md = new Markdownify_Extra($leap, MDFY_BODYWIDTH, $keephtml);
- } else {
- $md = new Markdownify($leap, MDFY_BODYWIDTH, $keephtml);
- }
- if (ini_get('magic_quotes_gpc')) {
- $_POST['input'] = stripslashes($_POST['input']);
- }
- $output = $md->parseString($_POST['input']);
- } else {
- $_POST['input'] = '';
- }
-?><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US">
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
- <title>HTML to Markdown Converter</title>
- </head>
- <body>
- <?php if (empty($_POST['input'])): ?>
- <form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
- <fieldset>
- <legend>HTML Input</legend>
- <textarea style="width:100%;" cols="85" rows="40" name="input"><?php echo htmlspecialchars($_POST['input'], ENT_NOQUOTES, 'UTF-8'); ?></textarea>
- </fieldset>
- <label for="extra">Markdownify Extra: <input name="extra" checked="checked" id="extra" type="checkbox" value="1" /></label>
- <label for="leap">Links after each block elem: <input name="leap" id="leap" type="checkbox" value="1" /></label>
- <label for="keepHTML">keep HTML: <input name="keepHTML" id="keepHTML" type="checkbox" value="1" checked="checked" /></label>
- <input type="submit" name="submit" value="submit" />
- </form>
- <?php else: ?>
- <h1 style="text-align:right;"><a href="<?php echo $_SERVER['PHP_SELF']; ?>">BACK</a></h1>
- <pre><?php echo htmlspecialchars($output, ENT_NOQUOTES, 'UTF-8'); ?></pre>
- <?php endif; ?>
- </body>
-</html> \ No newline at end of file
diff --git a/library/markdownify/markdownify.php b/library/markdownify/markdownify.php
deleted file mode 100644
index 0d4429a01..000000000
--- a/library/markdownify/markdownify.php
+++ /dev/null
@@ -1,1197 +0,0 @@
-<?php
-/**
- * Markdownify converts HTML Markup to [Markdown][1] (by [John Gruber][2]. It
- * also supports [Markdown Extra][3] by [Michel Fortin][4] via Markdownify_Extra.
- *
- * It all started as `html2text.php` - a port of [Aaron Swartz'][5] [`html2text.py`][6] - but
- * got a long way since. This is far more than a mere port now!
- * Starting with version 2.0.0 this is a complete rewrite and cannot be
- * compared to Aaron Swatz' `html2text.py` anylonger. I'm now using a HTML parser
- * (see `parsehtml.php` which I also wrote) which makes most of the evil
- * RegEx magic go away and additionally it gives a much cleaner class
- * structure. Also notably is the fact that I now try to prevent regressions by
- * utilizing testcases of Michel Fortin's [MDTest][7].
- *
- * [1]: http://daringfireball.com/projects/markdown
- * [2]: http://daringfireball.com/
- * [3]: http://www.michelf.com/projects/php-markdown/extra/
- * [4]: http://www.michelf.com/
- * [5]: http://www.aaronsw.com/
- * [6]: http://www.aaronsw.com/2002/html2text/
- * [7]: http://article.gmane.org/gmane.text.markdown.general/2540
- *
- * @version 2.0.0 alpha
- * @author Milian Wolff (<mail@milianw.de>, <http://milianw.de>)
- * @license LGPL, see LICENSE_LGPL.txt and the summary below
- * @copyright (C) 2007 Milian Wolff
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/**
- * HTML Parser, see http://sf.net/projects/parseHTML
- */
-require_once dirname(__FILE__).'/parsehtml/parsehtml.php';
-
-/**
- * default configuration
- */
-define('MDFY_LINKS_EACH_PARAGRAPH', false);
-define('MDFY_BODYWIDTH', false);
-define('MDFY_KEEPHTML', true);
-
-/**
- * HTML to Markdown converter class
- */
-class Markdownify {
- /**
- * html parser object
- *
- * @var parseHTML
- */
- var $parser;
- /**
- * markdown output
- *
- * @var string
- */
- var $output;
- /**
- * stack with tags which where not converted to html
- *
- * @var array<string>
- */
- var $notConverted = array();
- /**
- * skip conversion to markdown
- *
- * @var bool
- */
- var $skipConversion = false;
- /* options */
- /**
- * keep html tags which cannot be converted to markdown
- *
- * @var bool
- */
- var $keepHTML = false;
- /**
- * wrap output, set to 0 to skip wrapping
- *
- * @var int
- */
- var $bodyWidth = 0;
- /**
- * minimum body width
- *
- * @var int
- */
- var $minBodyWidth = 25;
- /**
- * display links after each paragraph
- *
- * @var bool
- */
- var $linksAfterEachParagraph = false;
- /**
- * constructor, set options, setup parser
- *
- * @param bool $linksAfterEachParagraph wether or not to flush stacked links after each paragraph
- * defaults to false
- * @param int $bodyWidth wether or not to wrap the output to the given width
- * defaults to false
- * @param bool $keepHTML wether to keep non markdownable HTML or to discard it
- * defaults to true (HTML will be kept)
- * @return void
- */
- function Markdownify($linksAfterEachParagraph = MDFY_LINKS_EACH_PARAGRAPH, $bodyWidth = MDFY_BODYWIDTH, $keepHTML = MDFY_KEEPHTML) {
- $this->linksAfterEachParagraph = $linksAfterEachParagraph;
- $this->keepHTML = $keepHTML;
-
- if ($bodyWidth > $this->minBodyWidth) {
- $this->bodyWidth = intval($bodyWidth);
- } else {
- $this->bodyWidth = false;
- }
-
- $this->parser = new parseHTML;
- $this->parser->noTagsInCode = true;
-
- # we don't have to do this every time
- $search = array();
- $replace = array();
- foreach ($this->escapeInText as $s => $r) {
- array_push($search, '#(?<!\\\)'.$s.'#U');
- array_push($replace, $r);
- }
- $this->escapeInText = array(
- 'search' => $search,
- 'replace' => $replace
- );
- }
- /**
- * parse a HTML string
- *
- * @param string $html
- * @return string markdown formatted
- */
- function parseString($html) {
- $this->parser->html = $html;
- $this->parse();
- return $this->output;
- }
- /**
- * tags with elements which can be handled by markdown
- *
- * @var array<string>
- */
- var $isMarkdownable = array(
- 'p' => array(),
- 'ul' => array(),
- 'ol' => array(),
- 'li' => array(),
- 'br' => array(),
- 'blockquote' => array(),
- 'code' => array(),
- 'pre' => array(),
- 'a' => array(
- 'href' => 'required',
- 'title' => 'optional',
- ),
- 'strong' => array(),
- 'b' => array(),
- 'em' => array(),
- 'i' => array(),
- 'img' => array(
- 'src' => 'required',
- 'alt' => 'optional',
- 'title' => 'optional',
- ),
- 'h1' => array(),
- 'h2' => array(),
- 'h3' => array(),
- 'h4' => array(),
- 'h5' => array(),
- 'h6' => array(),
- 'hr' => array(),
- );
- /**
- * html tags to be ignored (contents will be parsed)
- *
- * @var array<string>
- */
- var $ignore = array(
- 'html',
- 'body',
- );
- /**
- * html tags to be dropped (contents will not be parsed!)
- *
- * @var array<string>
- */
- var $drop = array(
- 'script',
- 'head',
- 'style',
- 'form',
- 'area',
- 'object',
- 'param',
- 'iframe',
- );
- /**
- * Markdown indents which could be wrapped
- * @note: use strings in regex format
- *
- * @var array<string>
- */
- var $wrappableIndents = array(
- '\* ', # ul
- '\d. ', # ol
- '\d\d. ', # ol
- '> ', # blockquote
- '', # p
- );
- /**
- * list of chars which have to be escaped in normal text
- * @note: use strings in regex format
- *
- * @var array
- *
- * TODO: what's with block chars / sequences at the beginning of a block?
- */
- var $escapeInText = array(
- '([-*_])([ ]{0,2}\1){2,}' => '\\\\$0|', # hr
- '\*\*([^*\s]+)\*\*' => '\*\*$1\*\*', # strong
- '\*([^*\s]+)\*' => '\*$1\*', # em
- '__(?! |_)(.+)(?!<_| )__' => '\_\_$1\_\_', # em
- '_(?! |_)(.+)(?!<_| )_' => '\_$1\_', # em
- '`(.+)`' => '\`$1\`', # code
- '\[(.+)\](\s*\()' => '\[$1\]$2', # links: [text] (url) => [text\] (url)
- '\[(.+)\](\s*)\[(.*)\]' => '\[$1\]$2\[$3\]', # links: [text][id] => [text\][id\]
- );
- /**
- * wether last processed node was a block tag or not
- *
- * @var bool
- */
- var $lastWasBlockTag = false;
- /**
- * name of last closed tag
- *
- * @var string
- */
- var $lastClosedTag = '';
- /**
- * iterate through the nodes and decide what we
- * shall do with the current node
- *
- * @param void
- * @return void
- */
- function parse() {
- $this->output = '';
- # drop tags
- $this->parser->html = preg_replace('#<('.implode('|', $this->drop).')[^>]*>.*</\\1>#sU', '', $this->parser->html);
- while ($this->parser->nextNode()) {
- switch ($this->parser->nodeType) {
- case 'doctype':
- break;
- case 'pi':
- case 'comment':
- if ($this->keepHTML) {
- $this->flushLinebreaks();
- $this->out($this->parser->node);
- $this->setLineBreaks(2);
- }
- # else drop
- break;
- case 'text':
- $this->handleText();
- break;
- case 'tag':
- if (in_array($this->parser->tagName, $this->ignore)) {
- break;
- }
- if ($this->parser->isStartTag) {
- $this->flushLinebreaks();
- }
- if ($this->skipConversion) {
- $this->isMarkdownable(); # update notConverted
- $this->handleTagToText();
- continue;
- }
- if (!$this->parser->keepWhitespace && $this->parser->isBlockElement && $this->parser->isStartTag) {
- $this->parser->html = ltrim($this->parser->html);
- }
- if ($this->isMarkdownable()) {
- if ($this->parser->isBlockElement && $this->parser->isStartTag && !$this->lastWasBlockTag && !empty($this->output)) {
- if (!empty($this->buffer)) {
- $str =& $this->buffer[count($this->buffer) -1];
- } else {
- $str =& $this->output;
- }
- if (substr($str, -strlen($this->indent)-1) != "\n".$this->indent) {
- $str .= "\n".$this->indent;
- }
- }
- $func = 'handleTag_'.$this->parser->tagName;
- $this->$func();
- if ($this->linksAfterEachParagraph && $this->parser->isBlockElement && !$this->parser->isStartTag && empty($this->parser->openTags)) {
- $this->flushStacked();
- }
- if (!$this->parser->isStartTag) {
- $this->lastClosedTag = $this->parser->tagName;
- }
- } else {
- $this->handleTagToText();
- $this->lastClosedTag = '';
- }
- break;
- default:
- trigger_error('invalid node type', E_USER_ERROR);
- break;
- }
- $this->lastWasBlockTag = $this->parser->nodeType == 'tag' && $this->parser->isStartTag && $this->parser->isBlockElement;
- }
- if (!empty($this->buffer)) {
- trigger_error('buffer was not flushed, this is a bug. please report!', E_USER_WARNING);
- while (!empty($this->buffer)) {
- $this->out($this->unbuffer());
- }
- }
- ### cleanup
- $this->output = rtrim(str_replace('&amp;', '&', str_replace('&lt;', '<', str_replace('&gt;', '>', $this->output))));
- # end parsing, flush stacked tags
- $this->flushStacked();
- $this->stack = array();
- }
- /**
- * check if current tag can be converted to Markdown
- *
- * @param void
- * @return bool
- */
- function isMarkdownable() {
- if (!isset($this->isMarkdownable[$this->parser->tagName])) {
- # simply not markdownable
- return false;
- }
- if ($this->parser->isStartTag) {
- $return = true;
- if ($this->keepHTML) {
- $diff = array_diff(array_keys($this->parser->tagAttributes), array_keys($this->isMarkdownable[$this->parser->tagName]));
- if (!empty($diff)) {
- # non markdownable attributes given
- $return = false;
- }
- }
- if ($return) {
- foreach ($this->isMarkdownable[$this->parser->tagName] as $attr => $type) {
- if ($type == 'required' && !isset($this->parser->tagAttributes[$attr])) {
- # required markdown attribute not given
- $return = false;
- break;
- }
- }
- }
- if (!$return) {
- array_push($this->notConverted, $this->parser->tagName.'::'.implode('/', $this->parser->openTags));
- }
- return $return;
- } else {
- if (!empty($this->notConverted) && end($this->notConverted) === $this->parser->tagName.'::'.implode('/', $this->parser->openTags)) {
- array_pop($this->notConverted);
- return false;
- }
- return true;
- }
- }
- /**
- * output all stacked tags
- *
- * @param void
- * @return void
- */
- function flushStacked() {
- # links
- foreach ($this->stack as $tag => $a) {
- if (!empty($a)) {
- call_user_func(array(&$this, 'flushStacked_'.$tag));
- }
- }
- }
- /**
- * output link references (e.g. [1]: http://example.com "title");
- *
- * @param void
- * @return void
- */
- function flushStacked_a() {
- $out = false;
- foreach ($this->stack['a'] as $k => $tag) {
- if (!isset($tag['unstacked'])) {
- if (!$out) {
- $out = true;
- $this->out("\n\n", true);
- } else {
- $this->out("\n", true);
- }
- $this->out(' ['.$tag['linkID'].']: '.$tag['href'].(isset($tag['title']) ? ' "'.$tag['title'].'"' : ''), true);
- $tag['unstacked'] = true;
- $this->stack['a'][$k] = $tag;
- }
- }
- }
- /**
- * flush enqued linebreaks
- *
- * @param void
- * @return void
- */
- function flushLinebreaks() {
- if ($this->lineBreaks && !empty($this->output)) {
- $this->out(str_repeat("\n".$this->indent, $this->lineBreaks), true);
- }
- $this->lineBreaks = 0;
- }
- /**
- * handle non Markdownable tags
- *
- * @param void
- * @return void
- */
- function handleTagToText() {
- if (!$this->keepHTML) {
- if (!$this->parser->isStartTag && $this->parser->isBlockElement) {
- $this->setLineBreaks(2);
- }
- } else {
- # dont convert to markdown inside this tag
- /** TODO: markdown extra **/
- if (!$this->parser->isEmptyTag) {
- if ($this->parser->isStartTag) {
- if (!$this->skipConversion) {
- $this->skipConversion = $this->parser->tagName.'::'.implode('/', $this->parser->openTags);
- }
- } else {
- if ($this->skipConversion == $this->parser->tagName.'::'.implode('/', $this->parser->openTags)) {
- $this->skipConversion = false;
- }
- }
- }
-
- if ($this->parser->isBlockElement) {
- if ($this->parser->isStartTag) {
- if (in_array($this->parent(), array('ins', 'del'))) {
- # looks like ins or del are block elements now
- $this->out("\n", true);
- $this->indent(' ');
- }
- if ($this->parser->tagName != 'pre') {
- $this->out($this->parser->node."\n".$this->indent);
- if (!$this->parser->isEmptyTag) {
- $this->indent(' ');
- } else {
- $this->setLineBreaks(1);
- }
- $this->parser->html = ltrim($this->parser->html);
- } else {
- # don't indent inside <pre> tags
- $this->out($this->parser->node);
- static $indent;
- $indent = $this->indent;
- $this->indent = '';
- }
- } else {
- if (!$this->parser->keepWhitespace) {
- $this->output = rtrim($this->output);
- }
- if ($this->parser->tagName != 'pre') {
- $this->indent(' ');
- $this->out("\n".$this->indent.$this->parser->node);
- } else {
- # reset indentation
- $this->out($this->parser->node);
- static $indent;
- $this->indent = $indent;
- }
-
- if (in_array($this->parent(), array('ins', 'del'))) {
- # ins or del was block element
- $this->out("\n");
- $this->indent(' ');
- }
- if ($this->parser->tagName == 'li') {
- $this->setLineBreaks(1);
- } else {
- $this->setLineBreaks(2);
- }
- }
- } else {
- $this->out($this->parser->node);
- }
- if (in_array($this->parser->tagName, array('code', 'pre'))) {
- if ($this->parser->isStartTag) {
- $this->buffer();
- } else {
- # add stuff so cleanup just reverses this
- $this->out(str_replace('&lt;', '&amp;lt;', str_replace('&gt;', '&amp;gt;', $this->unbuffer())));
- }
- }
- }
- }
- /**
- * handle plain text
- *
- * @param void
- * @return void
- */
- function handleText() {
- if ($this->hasParent('pre') && strpos($this->parser->node, "\n") !== false) {
- $this->parser->node = str_replace("\n", "\n".$this->indent, $this->parser->node);
- }
- if (!$this->hasParent('code') && !$this->hasParent('pre')) {
- # entity decode
- $this->parser->node = $this->decode($this->parser->node);
- if (!$this->skipConversion) {
- # escape some chars in normal Text
- $this->parser->node = preg_replace($this->escapeInText['search'], $this->escapeInText['replace'], $this->parser->node);
- }
- } else {
- $this->parser->node = str_replace(array('&quot;', '&apos'), array('"', '\''), $this->parser->node);
- }
- $this->out($this->parser->node);
- $this->lastClosedTag = '';
- }
- /**
- * handle <em> and <i> tags
- *
- * @param void
- * @return void
- */
- function handleTag_em() {
- $this->out('*', true);
- }
- function handleTag_i() {
- $this->handleTag_em();
- }
- /**
- * handle <strong> and <b> tags
- *
- * @param void
- * @return void
- */
- function handleTag_strong() {
- $this->out('**', true);
- }
- function handleTag_b() {
- $this->handleTag_strong();
- }
- /**
- * handle <h1> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h1() {
- $this->handleHeader(1);
- }
- /**
- * handle <h2> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h2() {
- $this->handleHeader(2);
- }
- /**
- * handle <h3> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h3() {
- $this->handleHeader(3);
- }
- /**
- * handle <h4> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h4() {
- $this->handleHeader(4);
- }
- /**
- * handle <h5> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h5() {
- $this->handleHeader(5);
- }
- /**
- * handle <h6> tags
- *
- * @param void
- * @return void
- */
- function handleTag_h6() {
- $this->handleHeader(6);
- }
- /**
- * number of line breaks before next inline output
- */
- var $lineBreaks = 0;
- /**
- * handle header tags (<h1> - <h6>)
- *
- * @param int $level 1-6
- * @return void
- */
- function handleHeader($level) {
- if ($this->parser->isStartTag) {
- $this->out(str_repeat('#', $level).' ', true);
- } else {
- $this->setLineBreaks(2);
- }
- }
- /**
- * handle <p> tags
- *
- * @param void
- * @return void
- */
- function handleTag_p() {
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(2);
- }
- }
- /**
- * handle <a> tags
- *
- * @param void
- * @return void
- */
- function handleTag_a() {
- if ($this->parser->isStartTag) {
- $this->buffer();
- if (isset($this->parser->tagAttributes['title'])) {
- $this->parser->tagAttributes['title'] = $this->decode($this->parser->tagAttributes['title']);
- } else {
- $this->parser->tagAttributes['title'] = null;
- }
- $this->parser->tagAttributes['href'] = $this->decode(trim($this->parser->tagAttributes['href']));
- $this->stack();
- } else {
- $tag = $this->unstack();
- $buffer = $this->unbuffer();
-
- if (empty($tag['href']) && empty($tag['title'])) {
- # empty links... testcase mania, who would possibly do anything like that?!
- $this->out('['.$buffer.']()', true);
- return;
- }
-
- if ($buffer == $tag['href'] && empty($tag['title'])) {
- # <http://example.com>
- $this->out('<'.$buffer.'>', true);
- return;
- }
-
- $bufferDecoded = $this->decode(trim($buffer));
- if (substr($tag['href'], 0, 7) == 'mailto:' && 'mailto:'.$bufferDecoded == $tag['href']) {
- if (is_null($tag['title'])) {
- # <mail@example.com>
- $this->out('<'.$bufferDecoded.'>', true);
- return;
- }
- # [mail@example.com][1]
- # ...
- # [1]: mailto:mail@example.com Title
- $tag['href'] = 'mailto:'.$bufferDecoded;
- }
-
- $this->out('['.$buffer.']('.$tag['href'].' "'.$tag['title'].'")', true);
-
-/*
- # [This link][id]
- foreach ($this->stack['a'] as $tag2) {
- if ($tag2['href'] == $tag['href'] && $tag2['title'] === $tag['title']) {
- $tag['linkID'] = $tag2['linkID'];
- break;
- }
- }
- if (!isset($tag['linkID'])) {
- $tag['linkID'] = count($this->stack['a']) + 1;
- array_push($this->stack['a'], $tag);
- }
-
- $this->out('['.$buffer.']['.$tag['linkID'].']', true);
-*/
- }
- }
- /**
- * handle <img /> tags
- *
- * @param void
- * @return void
- */
- function handleTag_img() {
- if (!$this->parser->isStartTag) {
- return; # just to be sure this is really an empty tag...
- }
-
- if (isset($this->parser->tagAttributes['title'])) {
- $this->parser->tagAttributes['title'] = $this->decode($this->parser->tagAttributes['title']);
- } else {
- $this->parser->tagAttributes['title'] = null;
- }
- if (isset($this->parser->tagAttributes['alt'])) {
- $this->parser->tagAttributes['alt'] = $this->decode($this->parser->tagAttributes['alt']);
- } else {
- $this->parser->tagAttributes['alt'] = null;
- }
-
- if (empty($this->parser->tagAttributes['src'])) {
- # support for "empty" images... dunno if this is really needed
- # but there are some testcases which do that...
- if (!empty($this->parser->tagAttributes['title'])) {
- $this->parser->tagAttributes['title'] = ' '.$this->parser->tagAttributes['title'].' ';
- }
- $this->out('!['.$this->parser->tagAttributes['alt'].']('.$this->parser->tagAttributes['title'].')', true);
- return;
- } else {
- $this->parser->tagAttributes['src'] = $this->decode($this->parser->tagAttributes['src']);
- }
-
-// ![Alt text](/path/to/img.jpg "Optional title")
- if ($this->parser->tagAttributes['title'] != "")
- $this->out('!['.$this->parser->tagAttributes['alt'].']('.$this->parser->tagAttributes['src'].' "'.$this->parser->tagAttributes['title'].'")', true);
- else
- $this->out('!['.$this->parser->tagAttributes['alt'].']('.$this->parser->tagAttributes['src'].')', true);
-
-/*
- # [This link][id]
- $link_id = false;
- if (!empty($this->stack['a'])) {
- foreach ($this->stack['a'] as $tag) {
- if ($tag['href'] == $this->parser->tagAttributes['src']
- && $tag['title'] === $this->parser->tagAttributes['title']) {
- $link_id = $tag['linkID'];
- break;
- }
- }
- } else {
- $this->stack['a'] = array();
- }
- if (!$link_id) {
- $link_id = count($this->stack['a']) + 1;
- $tag = array(
- 'href' => $this->parser->tagAttributes['src'],
- 'linkID' => $link_id,
- 'title' => $this->parser->tagAttributes['title']
- );
- array_push($this->stack['a'], $tag);
- }
-
- $this->out('!['.$this->parser->tagAttributes['alt'].']['.$link_id.']', true);
-*/
- }
- /**
- * handle <code> tags
- *
- * @param void
- * @return void
- */
- function handleTag_code() {
- if ($this->hasParent('pre')) {
- # ignore code blocks inside <pre>
- return;
- }
- if ($this->parser->isStartTag) {
- $this->buffer();
- } else {
- $buffer = $this->unbuffer();
- # use as many backticks as needed
- preg_match_all('#`+#', $buffer, $matches);
- if (!empty($matches[0])) {
- rsort($matches[0]);
-
- $ticks = '`';
- while (true) {
- if (!in_array($ticks, $matches[0])) {
- break;
- }
- $ticks .= '`';
- }
- } else {
- $ticks = '`';
- }
- if ($buffer[0] == '`' || substr($buffer, -1) == '`') {
- $buffer = ' '.$buffer.' ';
- }
- $this->out($ticks.$buffer.$ticks, true);
- }
- }
- /**
- * handle <pre> tags
- *
- * @param void
- * @return void
- */
- function handleTag_pre() {
- if ($this->keepHTML && $this->parser->isStartTag) {
- # check if a simple <code> follows
- if (!preg_match('#^\s*<code\s*>#Us', $this->parser->html)) {
- # this is no standard markdown code block
- $this->handleTagToText();
- return;
- }
- }
- $this->indent(' ');
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(2);
- } else {
- $this->parser->html = ltrim($this->parser->html);
- }
- }
- /**
- * handle <blockquote> tags
- *
- * @param void
- * @return void
- */
- function handleTag_blockquote() {
- $this->indent('> ');
- }
- /**
- * handle <ul> tags
- *
- * @param void
- * @return void
- */
- function handleTag_ul() {
- if ($this->parser->isStartTag) {
- $this->stack();
- if (!$this->keepHTML && $this->lastClosedTag == $this->parser->tagName) {
- $this->out("\n".$this->indent.'<!-- -->'."\n".$this->indent."\n".$this->indent);
- }
- } else {
- $this->unstack();
- if ($this->parent() != 'li' || preg_match('#^\s*(</li\s*>\s*<li\s*>\s*)?<(p|blockquote)\s*>#sU', $this->parser->html)) {
- # dont make Markdown add unneeded paragraphs
- $this->setLineBreaks(2);
- }
- }
- }
- /**
- * handle <ul> tags
- *
- * @param void
- * @return void
- */
- function handleTag_ol() {
- # same as above
- $this->parser->tagAttributes['num'] = 0;
- $this->handleTag_ul();
- }
- /**
- * handle <li> tags
- *
- * @param void
- * @return void
- */
- function handleTag_li() {
- if ($this->parent() == 'ol') {
- $parent =& $this->getStacked('ol');
- if ($this->parser->isStartTag) {
- $parent['num']++;
- $this->out($parent['num'].'.'.str_repeat(' ', 3 - strlen($parent['num'])), true);
- }
- $this->indent(' ', false);
- } else {
- if ($this->parser->isStartTag) {
- $this->out('* ', true);
- }
- $this->indent(' ', false);
- }
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(1);
- }
- }
- /**
- * handle <hr /> tags
- *
- * @param void
- * @return void
- */
- function handleTag_hr() {
- if (!$this->parser->isStartTag) {
- return; # just to be sure this really is an empty tag
- }
- $this->out('* * *', true);
- $this->setLineBreaks(2);
- }
- /**
- * handle <br /> tags
- *
- * @param void
- * @return void
- */
- function handleTag_br() {
- $this->out(" \n".$this->indent, true);
- $this->parser->html = ltrim($this->parser->html);
- }
- /**
- * node stack, e.g. for <a> and <abbr> tags
- *
- * @var array<array>
- */
- var $stack = array();
- /**
- * add current node to the stack
- * this only stores the attributes
- *
- * @param void
- * @return void
- */
- function stack() {
- if (!isset($this->stack[$this->parser->tagName])) {
- $this->stack[$this->parser->tagName] = array();
- }
- array_push($this->stack[$this->parser->tagName], $this->parser->tagAttributes);
- }
- /**
- * remove current tag from stack
- *
- * @param void
- * @return array
- */
- function unstack() {
- if (!isset($this->stack[$this->parser->tagName]) || !is_array($this->stack[$this->parser->tagName])) {
- trigger_error('Trying to unstack from empty stack. This must not happen.', E_USER_ERROR);
- }
- return array_pop($this->stack[$this->parser->tagName]);
- }
- /**
- * get last stacked element of type $tagName
- *
- * @param string $tagName
- * @return array
- */
- function & getStacked($tagName) {
- // no end() so it can be referenced
- return $this->stack[$tagName][count($this->stack[$tagName])-1];
- }
- /**
- * set number of line breaks before next start tag
- *
- * @param int $number
- * @return void
- */
- function setLineBreaks($number) {
- if ($this->lineBreaks < $number) {
- $this->lineBreaks = $number;
- }
- }
- /**
- * stores current buffers
- *
- * @var array<string>
- */
- var $buffer = array();
- /**
- * buffer next parser output until unbuffer() is called
- *
- * @param void
- * @return void
- */
- function buffer() {
- array_push($this->buffer, '');
- }
- /**
- * end current buffer and return buffered output
- *
- * @param void
- * @return string
- */
- function unbuffer() {
- return array_pop($this->buffer);
- }
- /**
- * append string to the correct var, either
- * directly to $this->output or to the current
- * buffers
- *
- * @param string $put
- * @return void
- */
- function out($put, $nowrap = false) {
- if (empty($put)) {
- return;
- }
- if (!empty($this->buffer)) {
- $this->buffer[count($this->buffer) - 1] .= $put;
- } else {
- if ($this->bodyWidth && !$this->parser->keepWhitespace) { # wrap lines
- // get last line
- $pos = strrpos($this->output, "\n");
- if ($pos === false) {
- $line = $this->output;
- } else {
- $line = substr($this->output, $pos);
- }
-
- if ($nowrap) {
- if ($put[0] != "\n" && $this->strlen($line) + $this->strlen($put) > $this->bodyWidth) {
- $this->output .= "\n".$this->indent.$put;
- } else {
- $this->output .= $put;
- }
- return;
- } else {
- $put .= "\n"; # make sure we get all lines in the while below
- $lineLen = $this->strlen($line);
- while ($pos = strpos($put, "\n")) {
- $putLine = substr($put, 0, $pos+1);
- $put = substr($put, $pos+1);
- $putLen = $this->strlen($putLine);
- if ($lineLen + $putLen < $this->bodyWidth) {
- $this->output .= $putLine;
- $lineLen = $putLen;
- } else {
- $split = preg_split('#^(.{0,'.($this->bodyWidth - $lineLen).'})\b#', $putLine, 2, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE);
- $this->output .= rtrim($split[1][0])."\n".$this->indent.$this->wordwrap(ltrim($split[2][0]), $this->bodyWidth, "\n".$this->indent, false);
- }
- }
- $this->output = substr($this->output, 0, -1);
- return;
- }
- } else {
- $this->output .= $put;
- }
- }
- }
- /**
- * current indentation
- *
- * @var string
- */
- var $indent = '';
- /**
- * indent next output (start tag) or unindent (end tag)
- *
- * @param string $str indentation
- * @param bool $output add indendation to output
- * @return void
- */
- function indent($str, $output = true) {
- if ($this->parser->isStartTag) {
- $this->indent .= $str;
- if ($output) {
- $this->out($str, true);
- }
- } else {
- $this->indent = substr($this->indent, 0, -strlen($str));
- }
- }
- /**
- * decode email addresses
- *
- * @author derernst@gmx.ch <http://www.php.net/manual/en/function.html-entity-decode.php#68536>
- * @author Milian Wolff <http://milianw.de>
- */
- function decode($text, $quote_style = ENT_QUOTES) {
- if (version_compare(PHP_VERSION, '5', '>=')) {
- # UTF-8 is only supported in PHP 5.x.x and above
- $text = html_entity_decode($text, $quote_style, 'UTF-8');
- } else {
- if (function_exists('html_entity_decode')) {
- $text = html_entity_decode($text, $quote_style, 'ISO-8859-1');
- } else {
- static $trans_tbl;
- if (!isset($trans_tbl)) {
- $trans_tbl = array_flip(get_html_translation_table(HTML_ENTITIES, $quote_style));
- }
- $text = strtr($text, $trans_tbl);
- }
- $text = preg_replace_callback('~&#x([0-9a-f]+);~i', array(&$this, '_decode_hex'), $text);
- $text = preg_replace_callback('~&#(\d{2,5});~', array(&$this, '_decode_numeric'), $text);
- }
- return $text;
- }
- /**
- * callback for decode() which converts a hexadecimal entity to UTF-8
- *
- * @param array $matches
- * @return string UTF-8 encoded
- */
- function _decode_hex($matches) {
- return $this->unichr(hexdec($matches[1]));
- }
- /**
- * callback for decode() which converts a numerical entity to UTF-8
- *
- * @param array $matches
- * @return string UTF-8 encoded
- */
- function _decode_numeric($matches) {
- return $this->unichr($matches[1]);
- }
- /**
- * UTF-8 chr() which supports numeric entities
- *
- * @author grey - greywyvern - com <http://www.php.net/manual/en/function.chr.php#55978>
- * @param array $matches
- * @return string UTF-8 encoded
- */
- function unichr($dec) {
- if ($dec < 128) {
- $utf = chr($dec);
- } else if ($dec < 2048) {
- $utf = chr(192 + (($dec - ($dec % 64)) / 64));
- $utf .= chr(128 + ($dec % 64));
- } else {
- $utf = chr(224 + (($dec - ($dec % 4096)) / 4096));
- $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
- $utf .= chr(128 + ($dec % 64));
- }
- return $utf;
- }
- /**
- * UTF-8 strlen()
- *
- * @param string $str
- * @return int
- *
- * @author dtorop 932 at hotmail dot com <http://www.php.net/manual/en/function.strlen.php#37975>
- * @author Milian Wolff <http://milianw.de>
- */
- function strlen($str) {
- if (function_exists('mb_strlen')) {
- return mb_strlen($str, 'UTF-8');
- } else {
- return preg_match_all('/[\x00-\x7F\xC0-\xFD]/', $str, $var_empty);
- }
- }
- /**
- * wordwrap for utf8 encoded strings
- *
- * @param string $str
- * @param integer $len
- * @param string $what
- * @return string
- */
- function wordwrap($str, $width, $break, $cut = false){
- if (!$cut) {
- $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){1,'.$width.'}\b#';
- } else {
- $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){'.$width.'}#';
- }
- $return = '';
- while (preg_match($regexp, $str, $matches)) {
- $string = $matches[0];
- $str = ltrim(substr($str, strlen($string)));
- if (!$cut && isset($str[0]) && in_array($str[0], array('.', '!', ';', ':', '?', ','))) {
- $string .= $str[0];
- $str = ltrim(substr($str, 1));
- }
- $return .= $string.$break;
- }
- return $return.ltrim($str);
- }
- /**
- * check if current node has a $tagName as parent (somewhere, not only the direct parent)
- *
- * @param string $tagName
- * @return bool
- */
- function hasParent($tagName) {
- return in_array($tagName, $this->parser->openTags);
- }
- /**
- * get tagName of direct parent tag
- *
- * @param void
- * @return string $tagName
- */
- function parent() {
- return end($this->parser->openTags);
- }
-}
diff --git a/library/markdownify/markdownify_cli.php b/library/markdownify/markdownify_cli.php
deleted file mode 100755
index b3fffbd5c..000000000
--- a/library/markdownify/markdownify_cli.php
+++ /dev/null
@@ -1,33 +0,0 @@
-#!/usr/bin/php
-<?php
-require dirname(__FILE__) .'/markdownify_extra.php';
-
-function param($name, $default = false) {
- if (!in_array('--'.$name, $_SERVER['argv']))
- return $default;
- reset($_SERVER['argv']);
- while (each($_SERVER['argv'])) {
- if (current($_SERVER['argv']) == '--'.$name)
- break;
- }
- $value = next($_SERVER['argv']);
- if ($value === false || substr($value, 0, 2) == '--')
- return true;
- else
- return $value;
-}
-
-
-$input = stream_get_contents(STDIN);
-
-$linksAfterEachParagraph = param('links');
-$bodyWidth = param('width');
-$keepHTML = param('html', true);
-
-if (param('no_extra')) {
- $parser = new Markdownify($linksAfterEachParagraph, $bodyWidth, $keepHTML);
-} else {
- $parser = new Markdownify_Extra($linksAfterEachParagraph, $bodyWidth, $keepHTML);
-}
-
-echo $parser->parseString($input) ."\n"; \ No newline at end of file
diff --git a/library/markdownify/markdownify_extra.php b/library/markdownify/markdownify_extra.php
deleted file mode 100644
index e978a1c8a..000000000
--- a/library/markdownify/markdownify_extra.php
+++ /dev/null
@@ -1,489 +0,0 @@
-<?php
-/**
- * Class to convert HTML to Markdown with PHP Markdown Extra syntax support.
- *
- * @version 1.0.0 alpha
- * @author Milian Wolff (<mail@milianw.de>, <http://milianw.de>)
- * @license LGPL, see LICENSE_LGPL.txt and the summary below
- * @copyright (C) 2007 Milian Wolff
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/**
- * standard Markdownify class
- */
-require_once dirname(__FILE__).'/markdownify.php';
-
-class Markdownify_Extra extends Markdownify {
- /**
- * table data, including rows with content and the maximum width of each col
- *
- * @var array
- */
- var $table = array();
- /**
- * current col
- *
- * @var int
- */
- var $col = -1;
- /**
- * current row
- *
- * @var int
- */
- var $row = 0;
- /**
- * constructor, see Markdownify::Markdownify() for more information
- */
- function Markdownify_Extra($linksAfterEachParagraph = MDFY_LINKS_EACH_PARAGRAPH, $bodyWidth = MDFY_BODYWIDTH, $keepHTML = MDFY_KEEPHTML) {
- parent::Markdownify($linksAfterEachParagraph, $bodyWidth, $keepHTML);
-
- ### new markdownable tags & attributes
- # header ids: # foo {bar}
- $this->isMarkdownable['h1']['id'] = 'optional';
- $this->isMarkdownable['h2']['id'] = 'optional';
- $this->isMarkdownable['h3']['id'] = 'optional';
- $this->isMarkdownable['h4']['id'] = 'optional';
- $this->isMarkdownable['h5']['id'] = 'optional';
- $this->isMarkdownable['h6']['id'] = 'optional';
- # tables
- $this->isMarkdownable['table'] = array();
- $this->isMarkdownable['th'] = array(
- 'align' => 'optional',
- );
- $this->isMarkdownable['td'] = array(
- 'align' => 'optional',
- );
- $this->isMarkdownable['tr'] = array();
- array_push($this->ignore, 'thead');
- array_push($this->ignore, 'tbody');
- array_push($this->ignore, 'tfoot');
- # definition lists
- $this->isMarkdownable['dl'] = array();
- $this->isMarkdownable['dd'] = array();
- $this->isMarkdownable['dt'] = array();
- # footnotes
- $this->isMarkdownable['fnref'] = array(
- 'target' => 'required',
- );
- $this->isMarkdownable['footnotes'] = array();
- $this->isMarkdownable['fn'] = array(
- 'name' => 'required',
- );
- $this->parser->blockElements['fnref'] = false;
- $this->parser->blockElements['fn'] = true;
- $this->parser->blockElements['footnotes'] = true;
- # abbr
- $this->isMarkdownable['abbr'] = array(
- 'title' => 'required',
- );
- # build RegEx lookahead to decide wether table can pe parsed or not
- $inlineTags = array_keys($this->parser->blockElements, false);
- $colContents = '(?:[^<]|<(?:'.implode('|', $inlineTags).'|[^a-z]))+';
- $this->tableLookaheadHeader = '{
- ^\s*(?:<thead\s*>)?\s* # open optional thead
- <tr\s*>\s*(?: # start required row with headers
- <th(?:\s+align=("|\')(?:left|center|right)\1)?\s*> # header with optional align
- \s*'.$colContents.'\s* # contents
- </th>\s* # close header
- )+</tr> # close row with headers
- \s*(?:</thead>)? # close optional thead
- }sxi';
- $this->tdSubstitute = '\s*'.$colContents.'\s* # contents
- </td>\s*';
- $this->tableLookaheadBody = '{
- \s*(?:<tbody\s*>)?\s* # open optional tbody
- (?:<tr\s*>\s* # start row
- %s # cols to be substituted
- </tr>)+ # close row
- \s*(?:</tbody>)? # close optional tbody
- \s*</table> # close table
- }sxi';
- }
- /**
- * handle header tags (<h1> - <h6>)
- *
- * @param int $level 1-6
- * @return void
- */
- function handleHeader($level) {
- static $id = null;
- if ($this->parser->isStartTag) {
- if (isset($this->parser->tagAttributes['id'])) {
- $id = $this->parser->tagAttributes['id'];
- }
- } else {
- if (!is_null($id)) {
- $this->out(' {#'.$id.'}');
- $id = null;
- }
- }
- parent::handleHeader($level);
- }
- /**
- * handle <abbr> tags
- *
- * @param void
- * @return void
- */
- function handleTag_abbr() {
- if ($this->parser->isStartTag) {
- $this->stack();
- $this->buffer();
- } else {
- $tag = $this->unstack();
- $tag['text'] = $this->unbuffer();
- $add = true;
- foreach ($this->stack['abbr'] as $stacked) {
- if ($stacked['text'] == $tag['text']) {
- /** TODO: differing abbr definitions, i.e. different titles for same text **/
- $add = false;
- break;
- }
- }
- $this->out($tag['text']);
- if ($add) {
- array_push($this->stack['abbr'], $tag);
- }
- }
- }
- /**
- * flush stacked abbr tags
- *
- * @param void
- * @return void
- */
- function flushStacked_abbr() {
- $out = array();
- foreach ($this->stack['abbr'] as $k => $tag) {
- if (!isset($tag['unstacked'])) {
- array_push($out, ' *['.$tag['text'].']: '.$tag['title']);
- $tag['unstacked'] = true;
- $this->stack['abbr'][$k] = $tag;
- }
- }
- if (!empty($out)) {
- $this->out("\n\n".implode("\n", $out));
- }
- }
- /**
- * handle <table> tags
- *
- * @param void
- * @return void
- */
- function handleTag_table() {
- if ($this->parser->isStartTag) {
- # check if upcoming table can be converted
- if ($this->keepHTML) {
- if (preg_match($this->tableLookaheadHeader, $this->parser->html, $matches)) {
- # header seems good, now check body
- # get align & number of cols
- preg_match_all('#<th(?:\s+align=("|\')(left|right|center)\1)?\s*>#si', $matches[0], $cols);
- $regEx = '';
- $i = 1;
- $aligns = array();
- foreach ($cols[2] as $align) {
- $align = strtolower($align);
- array_push($aligns, $align);
- if (empty($align)) {
- $align = 'left'; # default value
- }
- $td = '\s+align=("|\')'.$align.'\\'.$i;
- $i++;
- if ($align == 'left') {
- # look for empty align or left
- $td = '(?:'.$td.')?';
- }
- $td = '<td'.$td.'\s*>';
- $regEx .= $td.$this->tdSubstitute;
- }
- $regEx = sprintf($this->tableLookaheadBody, $regEx);
- if (preg_match($regEx, $this->parser->html, $matches, null, strlen($matches[0]))) {
- # this is a markdownable table tag!
- $this->table = array(
- 'rows' => array(),
- 'col_widths' => array(),
- 'aligns' => $aligns,
- );
- $this->row = 0;
- } else {
- # non markdownable table
- $this->handleTagToText();
- }
- } else {
- # non markdownable table
- $this->handleTagToText();
- }
- } else {
- $this->table = array(
- 'rows' => array(),
- 'col_widths' => array(),
- 'aligns' => array(),
- );
- $this->row = 0;
- }
- } else {
- # finally build the table in Markdown Extra syntax
- $separator = array();
- # seperator with correct align identifikators
- foreach($this->table['aligns'] as $col => $align) {
- if (!$this->keepHTML && !isset($this->table['col_widths'][$col])) {
- break;
- }
- $left = ' ';
- $right = ' ';
- switch ($align) {
- case 'left':
- $left = ':';
- break;
- case 'center':
- $right = ':';
- $left = ':';
- case 'right':
- $right = ':';
- break;
- }
- array_push($separator, $left.str_repeat('-', $this->table['col_widths'][$col]).$right);
- }
- $separator = '|'.implode('|', $separator).'|';
-
- $rows = array();
- # add padding
- array_walk_recursive($this->table['rows'], array(&$this, 'alignTdContent'));
- $header = array_shift($this->table['rows']);
- array_push($rows, '| '.implode(' | ', $header).' |');
- array_push($rows, $separator);
- foreach ($this->table['rows'] as $row) {
- array_push($rows, '| '.implode(' | ', $row).' |');
- }
- $this->out(implode("\n".$this->indent, $rows));
- $this->table = array();
- $this->setLineBreaks(2);
- }
- }
- /**
- * properly pad content so it is aligned as whished
- * should be used with array_walk_recursive on $this->table['rows']
- *
- * @param string &$content
- * @param int $col
- * @return void
- */
- function alignTdContent(&$content, $col) {
- switch ($this->table['aligns'][$col]) {
- default:
- case 'left':
- $content .= str_repeat(' ', $this->table['col_widths'][$col] - $this->strlen($content));
- break;
- case 'right':
- $content = str_repeat(' ', $this->table['col_widths'][$col] - $this->strlen($content)).$content;
- break;
- case 'center':
- $paddingNeeded = $this->table['col_widths'][$col] - $this->strlen($content);
- $left = floor($paddingNeeded / 2);
- $right = $paddingNeeded - $left;
- $content = str_repeat(' ', $left).$content.str_repeat(' ', $right);
- break;
- }
- }
- /**
- * handle <tr> tags
- *
- * @param void
- * @return void
- */
- function handleTag_tr() {
- if ($this->parser->isStartTag) {
- $this->col = -1;
- } else {
- $this->row++;
- }
- }
- /**
- * handle <td> tags
- *
- * @param void
- * @return void
- */
- function handleTag_td() {
- if ($this->parser->isStartTag) {
- $this->col++;
- if (!isset($this->table['col_widths'][$this->col])) {
- $this->table['col_widths'][$this->col] = 0;
- }
- $this->buffer();
- } else {
- $buffer = trim($this->unbuffer());
- $this->table['col_widths'][$this->col] = max($this->table['col_widths'][$this->col], $this->strlen($buffer));
- $this->table['rows'][$this->row][$this->col] = $buffer;
- }
- }
- /**
- * handle <th> tags
- *
- * @param void
- * @return void
- */
- function handleTag_th() {
- if (!$this->keepHTML && !isset($this->table['rows'][1]) && !isset($this->table['aligns'][$this->col+1])) {
- if (isset($this->parser->tagAttributes['align'])) {
- $this->table['aligns'][$this->col+1] = $this->parser->tagAttributes['align'];
- } else {
- $this->table['aligns'][$this->col+1] = '';
- }
- }
- $this->handleTag_td();
- }
- /**
- * handle <dl> tags
- *
- * @param void
- * @return void
- */
- function handleTag_dl() {
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(2);
- }
- }
- /**
- * handle <dt> tags
- *
- * @param void
- * @return void
- **/
- function handleTag_dt() {
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(1);
- }
- }
- /**
- * handle <dd> tags
- *
- * @param void
- * @return void
- */
- function handleTag_dd() {
- if ($this->parser->isStartTag) {
- if (substr(ltrim($this->parser->html), 0, 3) == '<p>') {
- # next comes a paragraph, so we'll need an extra line
- $this->out("\n".$this->indent);
- } elseif (substr($this->output, -2) == "\n\n") {
- $this->output = substr($this->output, 0, -1);
- }
- $this->out(': ');
- $this->indent(' ', false);
- } else {
- # lookahead for next dt
- if (substr(ltrim($this->parser->html), 0, 4) == '<dt>') {
- $this->setLineBreaks(2);
- } else {
- $this->setLineBreaks(1);
- }
- $this->indent(' ');
- }
- }
- /**
- * handle <fnref /> tags (custom footnote references, see markdownify_extra::parseString())
- *
- * @param void
- * @return void
- */
- function handleTag_fnref() {
- $this->out('[^'.$this->parser->tagAttributes['target'].']');
- }
- /**
- * handle <fn> tags (custom footnotes, see markdownify_extra::parseString()
- * and markdownify_extra::_makeFootnotes())
- *
- * @param void
- * @return void
- */
- function handleTag_fn() {
- if ($this->parser->isStartTag) {
- $this->out('[^'.$this->parser->tagAttributes['name'].']:');
- $this->setLineBreaks(1);
- } else {
- $this->setLineBreaks(2);
- }
- $this->indent(' ');
- }
- /**
- * handle <footnotes> tag (custom footnotes, see markdownify_extra::parseString()
- * and markdownify_extra::_makeFootnotes())
- *
- * @param void
- * @return void
- */
- function handleTag_footnotes() {
- if (!$this->parser->isStartTag) {
- $this->setLineBreaks(2);
- }
- }
- /**
- * parse a HTML string, clean up footnotes prior
- *
- * @param string $HTML input
- * @return string Markdown formatted output
- */
- function parseString($html) {
- /** TODO: custom markdown-extra options, e.g. titles & classes **/
- # <sup id="fnref:..."><a href"#fn..." rel="footnote">...</a></sup>
- # => <fnref target="..." />
- $html = preg_replace('@<sup id="fnref:([^"]+)">\s*<a href="#fn:\1" rel="footnote">\s*\d+\s*</a>\s*</sup>@Us', '<fnref target="$1" />', $html);
- # <div class="footnotes">
- # <hr />
- # <ol>
- #
- # <li id="fn:...">...</li>
- # ...
- #
- # </ol>
- # </div>
- # =>
- # <footnotes>
- # <fn name="...">...</fn>
- # ...
- # </footnotes>
- $html = preg_replace_callback('#<div class="footnotes">\s*<hr />\s*<ol>\s*(.+)\s*</ol>\s*</div>#Us', array(&$this, '_makeFootnotes'), $html);
- return parent::parseString($html);
- }
- /**
- * replace HTML representation of footnotes with something more easily parsable
- *
- * @note this is a callback to be used in parseString()
- *
- * @param array $matches
- * @return string
- */
- function _makeFootnotes($matches) {
- # <li id="fn:1">
- # ...
- # <a href="#fnref:block" rev="footnote">&#8617;</a></p>
- # </li>
- # => <fn name="1">...</fn>
- # remove footnote link
- $fns = preg_replace('@\s*(&#160;\s*)?<a href="#fnref:[^"]+" rev="footnote"[^>]*>&#8617;</a>\s*@s', '', $matches[1]);
- # remove empty paragraph
- $fns = preg_replace('@<p>\s*</p>@s', '', $fns);
- # <li id="fn:1">...</li> -> <footnote nr="1">...</footnote>
- $fns = str_replace('<li id="fn:', '<fn name="', $fns);
-
- $fns = '<footnotes>'.$fns.'</footnotes>';
- return preg_replace('#</li>\s*(?=(?:<fn|</footnotes>))#s', '</fn>$1', $fns);
- }
-} \ No newline at end of file
diff --git a/library/markdownify/parsehtml/parsehtml.php b/library/markdownify/parsehtml/parsehtml.php
deleted file mode 100644
index 1a8ecacda..000000000
--- a/library/markdownify/parsehtml/parsehtml.php
+++ /dev/null
@@ -1,618 +0,0 @@
-<?php
-/**
- * parseHTML is a HTML parser which works with PHP 4 and above.
- * It tries to handle invalid HTML to some degree.
- *
- * @version 1.0 beta
- * @author Milian Wolff (mail@milianw.de, http://milianw.de)
- * @license LGPL, see LICENSE_LGPL.txt and the summary below
- * @copyright (C) 2007 Milian Wolff
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-class parseHTML {
- /**
- * tags which are always empty (<br /> etc.)
- *
- * @var array<string>
- */
- var $emptyTags = array(
- 'br',
- 'hr',
- 'input',
- 'img',
- 'area',
- 'link',
- 'meta',
- 'param',
- );
- /**
- * tags with preformatted text
- * whitespaces wont be touched in them
- *
- * @var array<string>
- */
- var $preformattedTags = array(
- 'script',
- 'style',
- 'pre',
- 'code',
- );
- /**
- * supress HTML tags inside preformatted tags (see above)
- *
- * @var bool
- */
- var $noTagsInCode = false;
- /**
- * html to be parsed
- *
- * @var string
- */
- var $html = '';
- /**
- * node type:
- *
- * - tag (see isStartTag)
- * - text (includes cdata)
- * - comment
- * - doctype
- * - pi (processing instruction)
- *
- * @var string
- */
- var $nodeType = '';
- /**
- * current node content, i.e. either a
- * simple string (text node), or something like
- * <tag attrib="value"...>
- *
- * @var string
- */
- var $node = '';
- /**
- * wether current node is an opening tag (<a>) or not (</a>)
- * set to NULL if current node is not a tag
- * NOTE: empty tags (<br />) set this to true as well!
- *
- * @var bool | null
- */
- var $isStartTag = null;
- /**
- * wether current node is an empty tag (<br />) or not (<a></a>)
- *
- * @var bool | null
- */
- var $isEmptyTag = null;
- /**
- * tag name
- *
- * @var string | null
- */
- var $tagName = '';
- /**
- * attributes of current tag
- *
- * @var array (attribName=>value) | null
- */
- var $tagAttributes = null;
- /**
- * wether the current tag is a block element
- *
- * @var bool | null
- */
- var $isBlockElement = null;
-
- /**
- * keep whitespace
- *
- * @var int
- */
- var $keepWhitespace = 0;
- /**
- * list of open tags
- * count this to get current depth
- *
- * @var array
- */
- var $openTags = array();
- /**
- * list of block elements
- *
- * @var array
- * TODO: what shall we do with <del> and <ins> ?!
- */
- var $blockElements = array (
- # tag name => <bool> is block
- # block elements
- 'address' => true,
- 'blockquote' => true,
- 'center' => true,
- 'del' => true,
- 'dir' => true,
- 'div' => true,
- 'dl' => true,
- 'fieldset' => true,
- 'form' => true,
- 'h1' => true,
- 'h2' => true,
- 'h3' => true,
- 'h4' => true,
- 'h5' => true,
- 'h6' => true,
- 'hr' => true,
- 'ins' => true,
- 'isindex' => true,
- 'menu' => true,
- 'noframes' => true,
- 'noscript' => true,
- 'ol' => true,
- 'p' => true,
- 'pre' => true,
- 'table' => true,
- 'ul' => true,
- # set table elements and list items to block as well
- 'thead' => true,
- 'tbody' => true,
- 'tfoot' => true,
- 'td' => true,
- 'tr' => true,
- 'th' => true,
- 'li' => true,
- 'dd' => true,
- 'dt' => true,
- # header items and html / body as well
- 'html' => true,
- 'body' => true,
- 'head' => true,
- 'meta' => true,
- 'link' => true,
- 'style' => true,
- 'title' => true,
- # unfancy media tags, when indented should be rendered as block
- 'map' => true,
- 'object' => true,
- 'param' => true,
- 'embed' => true,
- 'area' => true,
- # inline elements
- 'a' => false,
- 'abbr' => false,
- 'acronym' => false,
- 'applet' => false,
- 'b' => false,
- 'basefont' => false,
- 'bdo' => false,
- 'big' => false,
- 'br' => false,
- 'button' => false,
- 'cite' => false,
- 'code' => false,
- 'del' => false,
- 'dfn' => false,
- 'em' => false,
- 'font' => false,
- 'i' => false,
- 'img' => false,
- 'ins' => false,
- 'input' => false,
- 'iframe' => false,
- 'kbd' => false,
- 'label' => false,
- 'q' => false,
- 'samp' => false,
- 'script' => false,
- 'select' => false,
- 'small' => false,
- 'span' => false,
- 'strong' => false,
- 'sub' => false,
- 'sup' => false,
- 'textarea' => false,
- 'tt' => false,
- 'var' => false,
- );
- /**
- * get next node, set $this->html prior!
- *
- * @param void
- * @return bool
- */
- function nextNode() {
- if (empty($this->html)) {
- # we are done with parsing the html string
- return false;
- }
- static $skipWhitespace = true;
- if ($this->isStartTag && !$this->isEmptyTag) {
- array_push($this->openTags, $this->tagName);
- if (in_array($this->tagName, $this->preformattedTags)) {
- # dont truncate whitespaces for <code> or <pre> contents
- $this->keepWhitespace++;
- }
- }
-
- if ($this->html[0] == '<') {
- $token = substr($this->html, 0, 9);
- if (substr($token, 0, 2) == '<?') {
- # xml prolog or other pi's
- /** TODO **/
- #trigger_error('this might need some work', E_USER_NOTICE);
- $pos = strpos($this->html, '>');
- $this->setNode('pi', $pos + 1);
- return true;
- }
- if (substr($token, 0, 4) == '<!--') {
- # comment
- $pos = strpos($this->html, '-->');
- if ($pos === false) {
- # could not find a closing -->, use next gt instead
- # this is firefox' behaviour
- $pos = strpos($this->html, '>') + 1;
- } else {
- $pos += 3;
- }
- $this->setNode('comment', $pos);
-
- $skipWhitespace = true;
- return true;
- }
- if ($token == '<!DOCTYPE') {
- # doctype
- $this->setNode('doctype', strpos($this->html, '>')+1);
-
- $skipWhitespace = true;
- return true;
- }
- if ($token == '<![CDATA[') {
- # cdata, use text node
-
- # remove leading <![CDATA[
- $this->html = substr($this->html, 9);
-
- $this->setNode('text', strpos($this->html, ']]>')+3);
-
- # remove trailing ]]> and trim
- $this->node = substr($this->node, 0, -3);
- $this->handleWhitespaces();
-
- $skipWhitespace = true;
- return true;
- }
- if ($this->parseTag()) {
- # seems to be a tag
- # handle whitespaces
- if ($this->isBlockElement) {
- $skipWhitespace = true;
- } else {
- $skipWhitespace = false;
- }
- return true;
- }
- }
- if ($this->keepWhitespace) {
- $skipWhitespace = false;
- }
- # when we get here it seems to be a text node
- $pos = strpos($this->html, '<');
- if ($pos === false) {
- $pos = strlen($this->html);
- }
- $this->setNode('text', $pos);
- $this->handleWhitespaces();
- if ($skipWhitespace && $this->node == ' ') {
- return $this->nextNode();
- }
- $skipWhitespace = false;
- return true;
- }
- /**
- * parse tag, set tag name and attributes, see if it's a closing tag and so forth...
- *
- * @param void
- * @return bool
- */
- function parseTag() {
- static $a_ord, $z_ord, $special_ords;
- if (!isset($a_ord)) {
- $a_ord = ord('a');
- $z_ord = ord('z');
- $special_ords = array(
- ord(':'), // for xml:lang
- ord('-'), // for http-equiv
- );
- }
-
- $tagName = '';
-
- $pos = 1;
- $isStartTag = $this->html[$pos] != '/';
- if (!$isStartTag) {
- $pos++;
- }
- # get tagName
- while (isset($this->html[$pos])) {
- $pos_ord = ord(strtolower($this->html[$pos]));
- if (($pos_ord >= $a_ord && $pos_ord <= $z_ord) || (!empty($tagName) && is_numeric($this->html[$pos]))) {
- $tagName .= $this->html[$pos];
- $pos++;
- } else {
- $pos--;
- break;
- }
- }
-
- $tagName = strtolower($tagName);
- if (empty($tagName) || !isset($this->blockElements[$tagName])) {
- # something went wrong => invalid tag
- $this->invalidTag();
- return false;
- }
- if ($this->noTagsInCode && end($this->openTags) == 'code' && !($tagName == 'code' && !$isStartTag)) {
- # we supress all HTML tags inside code tags
- $this->invalidTag();
- return false;
- }
-
- # get tag attributes
- /** TODO: in html 4 attributes do not need to be quoted **/
- $isEmptyTag = false;
- $attributes = array();
- $currAttrib = '';
- while (isset($this->html[$pos+1])) {
- $pos++;
- # close tag
- if ($this->html[$pos] == '>' || $this->html[$pos].$this->html[$pos+1] == '/>') {
- if ($this->html[$pos] == '/') {
- $isEmptyTag = true;
- $pos++;
- }
- break;
- }
-
- $pos_ord = ord(strtolower($this->html[$pos]));
- if ( ($pos_ord >= $a_ord && $pos_ord <= $z_ord) || in_array($pos_ord, $special_ords)) {
- # attribute name
- $currAttrib .= $this->html[$pos];
- } elseif (in_array($this->html[$pos], array(' ', "\t", "\n"))) {
- # drop whitespace
- } elseif (in_array($this->html[$pos].$this->html[$pos+1], array('="', "='"))) {
- # get attribute value
- $pos++;
- $await = $this->html[$pos]; # single or double quote
- $pos++;
- $value = '';
- while (isset($this->html[$pos]) && $this->html[$pos] != $await) {
- $value .= $this->html[$pos];
- $pos++;
- }
- $attributes[$currAttrib] = $value;
- $currAttrib = '';
- } else {
- $this->invalidTag();
- return false;
- }
- }
- if ($this->html[$pos] != '>') {
- $this->invalidTag();
- return false;
- }
-
- if (!empty($currAttrib)) {
- # html 4 allows something like <option selected> instead of <option selected="selected">
- $attributes[$currAttrib] = $currAttrib;
- }
- if (!$isStartTag) {
- if (!empty($attributes) || $tagName != end($this->openTags)) {
- # end tags must not contain any attributes
- # or maybe we did not expect a different tag to be closed
- $this->invalidTag();
- return false;
- }
- array_pop($this->openTags);
- if (in_array($tagName, $this->preformattedTags)) {
- $this->keepWhitespace--;
- }
- }
- $pos++;
- $this->node = substr($this->html, 0, $pos);
- $this->html = substr($this->html, $pos);
- $this->tagName = $tagName;
- $this->tagAttributes = $attributes;
- $this->isStartTag = $isStartTag;
- $this->isEmptyTag = $isEmptyTag || in_array($tagName, $this->emptyTags);
- if ($this->isEmptyTag) {
- # might be not well formed
- $this->node = preg_replace('# */? *>$#', ' />', $this->node);
- }
- $this->nodeType = 'tag';
- $this->isBlockElement = $this->blockElements[$tagName];
- return true;
- }
- /**
- * handle invalid tags
- *
- * @param void
- * @return void
- */
- function invalidTag() {
- $this->html = substr_replace($this->html, '&lt;', 0, 1);
- }
- /**
- * update all vars and make $this->html shorter
- *
- * @param string $type see description for $this->nodeType
- * @param int $pos to which position shall we cut?
- * @return void
- */
- function setNode($type, $pos) {
- if ($this->nodeType == 'tag') {
- # set tag specific vars to null
- # $type == tag should not be called here
- # see this::parseTag() for more
- $this->tagName = null;
- $this->tagAttributes = null;
- $this->isStartTag = null;
- $this->isEmptyTag = null;
- $this->isBlockElement = null;
-
- }
- $this->nodeType = $type;
- $this->node = substr($this->html, 0, $pos);
- $this->html = substr($this->html, $pos);
- }
- /**
- * check if $this->html begins with $str
- *
- * @param string $str
- * @return bool
- */
- function match($str) {
- return substr($this->html, 0, strlen($str)) == $str;
- }
- /**
- * truncate whitespaces
- *
- * @param void
- * @return void
- */
- function handleWhitespaces() {
- if ($this->keepWhitespace) {
- # <pre> or <code> before...
- return;
- }
- # truncate multiple whitespaces to a single one
- $this->node = preg_replace('#\s+#s', ' ', $this->node);
- }
- /**
- * normalize self::node
- *
- * @param void
- * @return void
- */
- function normalizeNode() {
- $this->node = '<';
- if (!$this->isStartTag) {
- $this->node .= '/'.$this->tagName.'>';
- return;
- }
- $this->node .= $this->tagName;
- foreach ($this->tagAttributes as $name => $value) {
- $this->node .= ' '.$name.'="'.str_replace('"', '&quot;', $value).'"';
- }
- if ($this->isEmptyTag) {
- $this->node .= ' /';
- }
- $this->node .= '>';
- }
-}
-
-/**
- * indent a HTML string properly
- *
- * @param string $html
- * @param string $indent optional
- * @return string
- */
-function indentHTML($html, $indent = " ", $noTagsInCode = false) {
- $parser = new parseHTML;
- $parser->noTagsInCode = $noTagsInCode;
- $parser->html = $html;
- $html = '';
- $last = true; # last tag was block elem
- $indent_a = array();
- while($parser->nextNode()) {
- if ($parser->nodeType == 'tag') {
- $parser->normalizeNode();
- }
- if ($parser->nodeType == 'tag' && $parser->isBlockElement) {
- $isPreOrCode = in_array($parser->tagName, array('code', 'pre'));
- if (!$parser->keepWhitespace && !$last && !$isPreOrCode) {
- $html = rtrim($html)."\n";
- }
- if ($parser->isStartTag) {
- $html .= implode($indent_a);
- if (!$parser->isEmptyTag) {
- array_push($indent_a, $indent);
- }
- } else {
- array_pop($indent_a);
- if (!$isPreOrCode) {
- $html .= implode($indent_a);
- }
- }
- $html .= $parser->node;
- if (!$parser->keepWhitespace && !($isPreOrCode && $parser->isStartTag)) {
- $html .= "\n";
- }
- $last = true;
- } else {
- if ($parser->nodeType == 'tag' && $parser->tagName == 'br') {
- $html .= $parser->node."\n";
- $last = true;
- continue;
- } elseif ($last && !$parser->keepWhitespace) {
- $html .= implode($indent_a);
- $parser->node = ltrim($parser->node);
- }
- $html .= $parser->node;
-
- if (in_array($parser->nodeType, array('comment', 'pi', 'doctype'))) {
- $html .= "\n";
- } else {
- $last = false;
- }
- }
- }
- return $html;
-}
-/*
-# testcase / example
-error_reporting(E_ALL);
-
-$html = '<p>Simple block on one line:</p>
-
-<div>foo</div>
-
-<p>And nested without indentation:</p>
-
-<div>
-<div>
-<div>
-foo
-</div>
-<div style=">"/>
-</div>
-<div>bar</div>
-</div>
-
-<p>And with attributes:</p>
-
-<div>
- <div id="foo">
- </div>
-</div>
-
-<p>This was broken in 1.0.2b7:</p>
-
-<div class="inlinepage">
-<div class="toggleableend">
-foo
-</div>
-</div>';
-#$html = '<a href="asdfasdf" title=\'asdf\' foo="bar">asdf</a>';
-echo indentHTML($html);
-die();
-*/
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
index 8b8d3dd58..2adb2fe53 100644
--- a/vendor/composer/autoload_classmap.php
+++ b/vendor/composer/autoload_classmap.php
@@ -7,6 +7,9 @@ $baseDir = dirname($vendorDir);
return array(
'Hubzilla\\Import\\Import' => $baseDir . '/include/Import/Importer.php',
+ 'Markdownify\\Converter' => $vendorDir . '/pixel418/markdownify/src/Converter.php',
+ 'Markdownify\\ConverterExtra' => $vendorDir . '/pixel418/markdownify/src/ConverterExtra.php',
+ 'Markdownify\\Parser' => $vendorDir . '/pixel418/markdownify/src/Parser.php',
'Michelf\\Markdown' => $vendorDir . '/michelf/php-markdown/Michelf/Markdown.php',
'Michelf\\MarkdownExtra' => $vendorDir . '/michelf/php-markdown/Michelf/MarkdownExtra.php',
'Michelf\\MarkdownInterface' => $vendorDir . '/michelf/php-markdown/Michelf/MarkdownInterface.php',
@@ -18,6 +21,8 @@ return array(
'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
+ 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
+ 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Sabre\\CalDAV\\Backend\\AbstractBackend' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php',
'Sabre\\CalDAV\\Backend\\BackendInterface' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php',
'Sabre\\CalDAV\\Backend\\NotificationSupport' => $vendorDir . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php',
@@ -268,7 +273,6 @@ return array(
'Sabre\\HTTP\\URLUtil' => $vendorDir . '/sabre/http/lib/URLUtil.php',
'Sabre\\HTTP\\Util' => $vendorDir . '/sabre/http/lib/Util.php',
'Sabre\\HTTP\\Version' => $vendorDir . '/sabre/http/lib/Version.php',
- 'Sabre\\Uri\\InvalidUriException' => $vendorDir . '/sabre/uri/lib/InvalidUriException.php',
'Sabre\\Uri\\Version' => $vendorDir . '/sabre/uri/lib/Version.php',
'Sabre\\VObject\\BirthdayCalendarGenerator' => $vendorDir . '/sabre/vobject/lib/BirthdayCalendarGenerator.php',
'Sabre\\VObject\\Cli' => $vendorDir . '/sabre/vobject/lib/Cli.php',
@@ -357,6 +361,9 @@ return array(
'Sabre\\Xml\\Writer' => $vendorDir . '/sabre/xml/lib/Writer.php',
'Sabre\\Xml\\XmlDeserializable' => $vendorDir . '/sabre/xml/lib/XmlDeserializable.php',
'Sabre\\Xml\\XmlSerializable' => $vendorDir . '/sabre/xml/lib/XmlSerializable.php',
+ 'Test\\Markdownify\\ConverterExtraTest' => $vendorDir . '/pixel418/markdownify/test/ConverterExtraTest.php',
+ 'Test\\Markdownify\\ConverterTest' => $vendorDir . '/pixel418/markdownify/test/ConverterTest.php',
+ 'Test\\Markdownify\\ConverterTestCase' => $vendorDir . '/pixel418/markdownify/test/ConverterTestCase.php',
'Zotlabs\\Access\\AccessList' => $baseDir . '/Zotlabs/Access/AccessList.php',
'Zotlabs\\Access\\PermissionLimits' => $baseDir . '/Zotlabs/Access/PermissionLimits.php',
'Zotlabs\\Access\\PermissionRoles' => $baseDir . '/Zotlabs/Access/PermissionRoles.php',
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
index e8ea2ed78..00a183cc1 100644
--- a/vendor/composer/autoload_psr4.php
+++ b/vendor/composer/autoload_psr4.php
@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
return array(
'Zotlabs\\' => array($baseDir . '/Zotlabs'),
+ 'Test\\Markdownify\\' => array($vendorDir . '/pixel418/markdownify/test'),
'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
@@ -17,5 +18,6 @@ return array(
'Sabre\\CardDAV\\' => array($vendorDir . '/sabre/dav/lib/CardDAV'),
'Sabre\\CalDAV\\' => array($vendorDir . '/sabre/dav/lib/CalDAV'),
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
+ 'Markdownify\\' => array($vendorDir . '/pixel418/markdownify/src'),
'Hubzilla\\' => array($baseDir . '/include'),
);
diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php
index 070595527..a7c84b7f1 100644
--- a/vendor/composer/autoload_static.php
+++ b/vendor/composer/autoload_static.php
@@ -21,6 +21,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
'Zotlabs\\' => 8,
),
+ 'T' =>
+ array (
+ 'Test\\Markdownify\\' => 17,
+ ),
'S' =>
array (
'Sabre\\Xml\\' => 10,
@@ -37,6 +41,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
'Psr\\Log\\' => 8,
),
+ 'M' =>
+ array (
+ 'Markdownify\\' => 12,
+ ),
'H' =>
array (
'Hubzilla\\' => 9,
@@ -48,6 +56,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
0 => __DIR__ . '/../..' . '/Zotlabs',
),
+ 'Test\\Markdownify\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/pixel418/markdownify/test',
+ ),
'Sabre\\Xml\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/xml/lib',
@@ -88,6 +100,10 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
array (
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
),
+ 'Markdownify\\' =>
+ array (
+ 0 => __DIR__ . '/..' . '/pixel418/markdownify/src',
+ ),
'Hubzilla\\' =>
array (
0 => __DIR__ . '/../..' . '/include',
@@ -106,6 +122,9 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
public static $classMap = array (
'Hubzilla\\Import\\Import' => __DIR__ . '/../..' . '/include/Import/Importer.php',
+ 'Markdownify\\Converter' => __DIR__ . '/..' . '/pixel418/markdownify/src/Converter.php',
+ 'Markdownify\\ConverterExtra' => __DIR__ . '/..' . '/pixel418/markdownify/src/ConverterExtra.php',
+ 'Markdownify\\Parser' => __DIR__ . '/..' . '/pixel418/markdownify/src/Parser.php',
'Michelf\\Markdown' => __DIR__ . '/..' . '/michelf/php-markdown/Michelf/Markdown.php',
'Michelf\\MarkdownExtra' => __DIR__ . '/..' . '/michelf/php-markdown/Michelf/MarkdownExtra.php',
'Michelf\\MarkdownInterface' => __DIR__ . '/..' . '/michelf/php-markdown/Michelf/MarkdownInterface.php',
@@ -117,6 +136,8 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php',
'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php',
'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php',
+ 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
+ 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php',
'Sabre\\CalDAV\\Backend\\AbstractBackend' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/AbstractBackend.php',
'Sabre\\CalDAV\\Backend\\BackendInterface' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/BackendInterface.php',
'Sabre\\CalDAV\\Backend\\NotificationSupport' => __DIR__ . '/..' . '/sabre/dav/lib/CalDAV/Backend/NotificationSupport.php',
@@ -367,7 +388,6 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Sabre\\HTTP\\URLUtil' => __DIR__ . '/..' . '/sabre/http/lib/URLUtil.php',
'Sabre\\HTTP\\Util' => __DIR__ . '/..' . '/sabre/http/lib/Util.php',
'Sabre\\HTTP\\Version' => __DIR__ . '/..' . '/sabre/http/lib/Version.php',
- 'Sabre\\Uri\\InvalidUriException' => __DIR__ . '/..' . '/sabre/uri/lib/InvalidUriException.php',
'Sabre\\Uri\\Version' => __DIR__ . '/..' . '/sabre/uri/lib/Version.php',
'Sabre\\VObject\\BirthdayCalendarGenerator' => __DIR__ . '/..' . '/sabre/vobject/lib/BirthdayCalendarGenerator.php',
'Sabre\\VObject\\Cli' => __DIR__ . '/..' . '/sabre/vobject/lib/Cli.php',
@@ -456,6 +476,9 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Sabre\\Xml\\Writer' => __DIR__ . '/..' . '/sabre/xml/lib/Writer.php',
'Sabre\\Xml\\XmlDeserializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlDeserializable.php',
'Sabre\\Xml\\XmlSerializable' => __DIR__ . '/..' . '/sabre/xml/lib/XmlSerializable.php',
+ 'Test\\Markdownify\\ConverterExtraTest' => __DIR__ . '/..' . '/pixel418/markdownify/test/ConverterExtraTest.php',
+ 'Test\\Markdownify\\ConverterTest' => __DIR__ . '/..' . '/pixel418/markdownify/test/ConverterTest.php',
+ 'Test\\Markdownify\\ConverterTestCase' => __DIR__ . '/..' . '/pixel418/markdownify/test/ConverterTestCase.php',
'Zotlabs\\Access\\AccessList' => __DIR__ . '/../..' . '/Zotlabs/Access/AccessList.php',
'Zotlabs\\Access\\PermissionLimits' => __DIR__ . '/../..' . '/Zotlabs/Access/PermissionLimits.php',
'Zotlabs\\Access\\PermissionRoles' => __DIR__ . '/../..' . '/Zotlabs/Access/PermissionRoles.php',
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index 73827081f..0fb88e5a1 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -518,5 +518,63 @@
"keywords": [
"markdown"
]
+ },
+ {
+ "name": "pixel418/markdownify",
+ "version": "v2.2.1",
+ "version_normalized": "2.2.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Elephant418/Markdownify.git",
+ "reference": "0160677f04c784550dd10fd72fdf3994967db848"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Elephant418/Markdownify/zipball/0160677f04c784550dd10fd72fdf3994967db848",
+ "reference": "0160677f04c784550dd10fd72fdf3994967db848",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8"
+ },
+ "time": "2016-09-21T13:01:43+00:00",
+ "type": "lib",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Markdownify\\": "src",
+ "Test\\Markdownify\\": "test"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "LGPL"
+ ],
+ "authors": [
+ {
+ "name": "Peter Kruithof",
+ "email": "pkruithof@gmail.com",
+ "homepage": "http://pkruithof.tumblr.com/"
+ },
+ {
+ "name": "Milian Wolff",
+ "email": "mail@milianw.de",
+ "homepage": "http://milianw.de"
+ },
+ {
+ "name": "Thomas Zilliox",
+ "email": "hello@tzi.fr",
+ "homepage": "http://tzi.fr"
+ }
+ ],
+ "description": "The HTML to Markdown converter for PHP ",
+ "homepage": "https://github.com/elephant418/Markdownify",
+ "keywords": [
+ "markdown",
+ "markdownify"
+ ]
}
]
diff --git a/vendor/pixel418/markdownify/CHANGELOG.md b/vendor/pixel418/markdownify/CHANGELOG.md
new file mode 100644
index 000000000..a0900fdf5
--- /dev/null
+++ b/vendor/pixel418/markdownify/CHANGELOG.md
@@ -0,0 +1,76 @@
+CHANGELOG
+==============
+
+
+21/09/2016 v2.2.1
+--------------
+
+ * Fix: Moving trailing whitespace from inline elements outside of the element
+ * Feature: Use PSR-4
+ * Feature: PHP 7.0 support in continuous integration
+ * Doc: Update of the README
+
+
+07/09/2016 v2.2.0
+--------------
+
+ * Fix: Reset state between each parsing
+
+
+19/02/2016 v2.1.11
+--------------
+
+ * Fix: Empty table cell conversion
+
+
+10/02/2016 v2.1.10
+--------------
+
+ * Fix: Handle nested table.
+
+
+01/04/2015 v2.1.9
+--------------
+
+ * Fix: Handle HTML breaks & spaces in a less destructive way.
+
+
+26/03/2015 v2.1.8
+--------------
+
+ * Fix: Use alternative italic character
+ * Fix: Handle HTML breaks inside another tag
+ * Fix: Handle HTML spaces around tags
+
+
+07/11/2014 v2.1.7
+--------------
+
+ * Change composer name to "elephant418/markdownify"
+
+
+14/07/2014 v2.1.6
+--------------
+
+ * Fix: Simulate a paragraph for inline text preceding block element
+ * Fix: Nested lists
+ * Fix: setKeepHTML method
+ * Feature: PHP 5.5 & 5.6 support in continuous integration
+
+
+16/03/2014 v2.1.5
+--------------
+
+Add display settings
+
+ * Test: Add tests for footnotes after every paragraph or not
+ * Feature: Allow to display link reference in paragraph, without footnotes
+
+
+27/02/2014 v2.1.4
+--------------
+
+Improve how ConverterExtra handle id & class attributes:
+
+ * Feature: Allow id & class attributes on links
+ * Feature: Allow class attributes on headings \ No newline at end of file
diff --git a/library/markdownify/LICENSE_LGPL.txt b/vendor/pixel418/markdownify/LICENSE
index 5ab7695ab..5ab7695ab 100644
--- a/library/markdownify/LICENSE_LGPL.txt
+++ b/vendor/pixel418/markdownify/LICENSE
diff --git a/vendor/pixel418/markdownify/README.md b/vendor/pixel418/markdownify/README.md
new file mode 100644
index 000000000..8855b0d05
--- /dev/null
+++ b/vendor/pixel418/markdownify/README.md
@@ -0,0 +1,63 @@
+# Markdownify
+
+[![Build Status](https://travis-ci.org/Elephant418/Markdownify.png?branch=master)](https://travis-ci.org/Elephant418/Markdownify?branch=master)
+[![Total Downloads](https://poser.pugx.org/pixel418/markdownify/downloads)](https://packagist.org/packages/pixel418/markdownify)
+[![License LGPL](https://poser.pugx.org/pixel418/markdownify/license)](https://opensource.org/licenses/lgpl-2.1.php)
+
+The HTML to Markdown converter for PHP
+
+[Code example](#code-example) | [How to Install](#how-to-install) | [How to Contribute](#how-to-contribute) | [Author & Community](#author--community)
+
+
+
+Code example
+--------
+
+### Markdown
+
+```php
+$converter = new Markdownify\Converter;
+$converter->parseString('<h1>Heading</h1>');
+// Returns: # Heading
+```
+
+### Markdown Extra [as defined by @michelf](http://michelf.ca/projects/php-markdown/extra/)
+
+```php
+$converter = new Markdownify\ConverterExtra;
+$converter->parseString('<h1 id="md">Heading</h1>');
+// Returns: # Heading {#md}
+```
+
+
+
+How to Install
+--------
+
+This library package requires `PHP 5.3` or later.<br>
+Install [Composer](http://getcomposer.org/doc/01-basic-usage.md#installation) and run the following command to get the latest version:
+
+```sh
+composer require pixel418/markdownify
+```
+
+
+
+How to Contribute
+--------
+
+1. Fork the Markdownify repository
+2. Create a new branch for each feature or improvement
+3. Send a pull request from each feature branch to the **v2.x** branch
+
+If you don't know much about pull request, you can read [the Github article](https://help.github.com/articles/using-pull-requests)
+
+
+
+Author & Community
+--------
+
+Markdownify is under [LGPL License](http://opensource.org/licenses/LGPL-2.1)<br>
+It was created by [Milian Wolff](http://milianw.de)<br>
+It was converted to a Symfony Bundle by [Peter Kruithof](https://github.com/pkruithof)<br>
+It is maintained by [Thomas ZILLIOX](http://tzi.fr)
diff --git a/vendor/pixel418/markdownify/composer.json b/vendor/pixel418/markdownify/composer.json
new file mode 100644
index 000000000..71d9f3565
--- /dev/null
+++ b/vendor/pixel418/markdownify/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "pixel418/markdownify",
+ "type": "lib",
+ "description": "The HTML to Markdown converter for PHP ",
+ "keywords": ["markdown", "markdownify"],
+ "license": "LGPL",
+ "homepage": "https://github.com/elephant418/Markdownify",
+ "authors": [
+ {
+ "name": "Milian Wolff",
+ "email": "mail@milianw.de",
+ "homepage": "http://milianw.de"
+
+ },
+ {
+ "name": "Peter Kruithof",
+ "email": "pkruithof@gmail.com",
+ "homepage": "http://pkruithof.tumblr.com/"
+ },
+ {
+ "name": "Thomas Zilliox",
+ "email": "hello@tzi.fr",
+ "homepage": "http://tzi.fr"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^4.8"
+ },
+ "autoload": {
+ "psr-4": {
+ "Markdownify\\": "src",
+ "Test\\Markdownify\\": "test"
+ }
+ }
+}
diff --git a/vendor/pixel418/markdownify/src/Converter.php b/vendor/pixel418/markdownify/src/Converter.php
new file mode 100644
index 000000000..77c62dc7e
--- /dev/null
+++ b/vendor/pixel418/markdownify/src/Converter.php
@@ -0,0 +1,1400 @@
+<?php
+
+/* This file is part of the Markdownify project, which is under LGPL license */
+
+namespace Markdownify;
+
+/**
+ * default configuration
+ */
+define('MDFY_BODYWIDTH', false);
+define('MDFY_KEEPHTML', true);
+
+/**
+ * HTML to Markdown converter class
+ */
+class Converter
+{
+ /**
+ * html parser object
+ *
+ * @var parseHTML
+ */
+ protected $parser;
+
+ /**
+ * markdown output
+ *
+ * @var string
+ */
+ protected $output;
+
+ /**
+ * stack with tags which where not converted to html
+ *
+ * @var array<string>
+ */
+ protected $notConverted = array();
+
+ /**
+ * skip conversion to markdown
+ *
+ * @var bool
+ */
+ protected $skipConversion = false;
+
+ /* options */
+
+ /**
+ * keep html tags which cannot be converted to markdown
+ *
+ * @var bool
+ */
+ protected $keepHTML = false;
+
+ /**
+ * wrap output, set to 0 to skip wrapping
+ *
+ * @var int
+ */
+ protected $bodyWidth = 0;
+
+ /**
+ * minimum body width
+ *
+ * @var int
+ */
+ protected $minBodyWidth = 25;
+
+ /**
+ * position where the link reference will be displayed
+ *
+ *
+ * @var int
+ */
+ protected $linkPosition;
+ const LINK_AFTER_CONTENT = 0;
+ const LINK_AFTER_PARAGRAPH = 1;
+ const LINK_IN_PARAGRAPH = 2;
+
+ /**
+ * stores current buffers
+ *
+ * @var array<string>
+ */
+ protected $buffer = array();
+
+ /**
+ * stores current buffers
+ *
+ * @var array<string>
+ */
+ protected $footnotes = array();
+
+ /**
+ * tags with elements which can be handled by markdown
+ *
+ * @var array<string>
+ */
+ protected $isMarkdownable = array(
+ 'p' => array(),
+ 'ul' => array(),
+ 'ol' => array(),
+ 'li' => array(),
+ 'br' => array(),
+ 'blockquote' => array(),
+ 'code' => array(),
+ 'pre' => array(),
+ 'a' => array(
+ 'href' => 'required',
+ 'title' => 'optional',
+ ),
+ 'strong' => array(),
+ 'b' => array(),
+ 'em' => array(),
+ 'i' => array(),
+ 'img' => array(
+ 'src' => 'required',
+ 'alt' => 'optional',
+ 'title' => 'optional',
+ ),
+ 'h1' => array(),
+ 'h2' => array(),
+ 'h3' => array(),
+ 'h4' => array(),
+ 'h5' => array(),
+ 'h6' => array(),
+ 'hr' => array(),
+ );
+
+ /**
+ * html tags to be ignored (contents will be parsed)
+ *
+ * @var array<string>
+ */
+ protected $ignore = array(
+ 'html',
+ 'body',
+ );
+
+ /**
+ * html tags to be dropped (contents will not be parsed!)
+ *
+ * @var array<string>
+ */
+ protected $drop = array(
+ 'script',
+ 'head',
+ 'style',
+ 'form',
+ 'area',
+ 'object',
+ 'param',
+ 'iframe',
+ );
+
+ /**
+ * html block tags that allow inline & block children
+ *
+ * @var array<string>
+ */
+ protected $allowMixedChildren = array(
+ 'li'
+ );
+
+ /**
+ * Markdown indents which could be wrapped
+ * @note: use strings in regex format
+ *
+ * @var array<string>
+ */
+ protected $wrappableIndents = array(
+ '\* ', // ul
+ '\d. ', // ol
+ '\d\d. ', // ol
+ '> ', // blockquote
+ '', // p
+ );
+
+ /**
+ * list of chars which have to be escaped in normal text
+ * @note: use strings in regex format
+ *
+ * @var array
+ *
+ * TODO: what's with block chars / sequences at the beginning of a block?
+ */
+ protected $escapeInText = array(
+ '\*\*([^*]+)\*\*' => '\*\*$1\*\*', // strong
+ '\*([^*]+)\*' => '\*$1\*', // em
+ '__(?! |_)(.+)(?!<_| )__' => '\_\_$1\_\_', // strong
+ '_(?! |_)(.+)(?!<_| )_' => '\_$1\_', // em
+ '([-*_])([ ]{0,2}\1){2,}' => '\\\\$0', // hr
+ '`' => '\`', // code
+ '\[(.+)\](\s*\()' => '\[$1\]$2', // links: [text] (url) => [text\] (url)
+ '\[(.+)\](\s*)\[(.*)\]' => '\[$1\]$2\[$3\]', // links: [text][id] => [text\][id\]
+ '^#(#{0,5}) ' => '\#$1 ', // header
+ );
+
+ /**
+ * wether last processed node was a block tag or not
+ *
+ * @var bool
+ */
+ protected $lastWasBlockTag = false;
+
+ /**
+ * name of last closed tag
+ *
+ * @var string
+ */
+ protected $lastClosedTag = '';
+
+ /**
+ * number of line breaks before next inline output
+ */
+ protected $lineBreaks = 0;
+
+ /**
+ * node stack, e.g. for <a> and <abbr> tags
+ *
+ * @var array<array>
+ */
+ protected $stack = array();
+
+ /**
+ * current indentation
+ *
+ * @var string
+ */
+ protected $indent = '';
+
+ /**
+ * constructor, set options, setup parser
+ *
+ * @param int $linkPosition define the position of links
+ * @param int $bodyWidth whether or not to wrap the output to the given width
+ * defaults to false
+ * @param bool $keepHTML whether to keep non markdownable HTML or to discard it
+ * defaults to true (HTML will be kept)
+ * @return void
+ */
+ public function __construct($linkPosition = self::LINK_AFTER_CONTENT, $bodyWidth = MDFY_BODYWIDTH, $keepHTML = MDFY_KEEPHTML)
+ {
+ $this->linkPosition = $linkPosition;
+ $this->keepHTML = $keepHTML;
+
+ if ($bodyWidth > $this->minBodyWidth) {
+ $this->bodyWidth = intval($bodyWidth);
+ } else {
+ $this->bodyWidth = false;
+ }
+
+ $this->parser = new Parser;
+ $this->parser->noTagsInCode = true;
+
+ // we don't have to do this every time
+ $search = array();
+ $replace = array();
+ foreach ($this->escapeInText as $s => $r) {
+ array_push($search, '@(?<!\\\)' . $s . '@U');
+ array_push($replace, $r);
+ }
+ $this->escapeInText = array(
+ 'search' => $search,
+ 'replace' => $replace
+ );
+ }
+
+ /**
+ * parse a HTML string
+ *
+ * @param string $html
+ * @return string markdown formatted
+ */
+ public function parseString($html)
+ {
+ $this->resetState();
+
+ $this->parser->html = $html;
+ $this->parse();
+
+ return $this->output;
+ }
+
+ /**
+ * set the position where the link reference will be displayed
+ *
+ * @param int $linkPosition
+ * @return void
+ */
+ public function setLinkPosition($linkPosition)
+ {
+ $this->linkPosition = $linkPosition;
+ }
+
+ /**
+ * set keep HTML tags which cannot be converted to markdown
+ *
+ * @param bool $linkPosition
+ * @return void
+ */
+ public function setKeepHTML($keepHTML)
+ {
+ $this->keepHTML = $keepHTML;
+ }
+
+ /**
+ * iterate through the nodes and decide what we
+ * shall do with the current node
+ *
+ * @param void
+ * @return void
+ */
+ protected function parse()
+ {
+ $this->output = '';
+ // drop tags
+ $this->parser->html = preg_replace('#<(' . implode('|', $this->drop) . ')[^>]*>.*</\\1>#sU', '', $this->parser->html);
+ while ($this->parser->nextNode()) {
+ switch ($this->parser->nodeType) {
+ case 'doctype':
+ break;
+ case 'pi':
+ case 'comment':
+ if ($this->keepHTML) {
+ $this->flushLinebreaks();
+ $this->out($this->parser->node);
+ $this->setLineBreaks(2);
+ }
+ // else drop
+ break;
+ case 'text':
+ $this->handleText();
+ break;
+ case 'tag':
+ if (in_array($this->parser->tagName, $this->ignore)) {
+ break;
+ }
+ // If the previous tag was not a block element, we simulate a paragraph tag
+ if ($this->parser->isBlockElement && $this->parser->isNextToInlineContext && !in_array($this->parent(), $this->allowMixedChildren)) {
+ $this->setLineBreaks(2);
+ }
+ if ($this->parser->isStartTag) {
+ $this->flushLinebreaks();
+ }
+ if ($this->skipConversion) {
+ $this->isMarkdownable(); // update notConverted
+ $this->handleTagToText();
+ continue;
+ }
+
+ // block elements
+ if (!$this->parser->keepWhitespace && $this->parser->isBlockElement) {
+ $this->fixBlockElementSpacing();
+ }
+
+ // inline elements
+ if (!$this->parser->keepWhitespace && $this->parser->isInlineContext) {
+ $this->fixInlineElementSpacing();
+ }
+
+ if ($this->isMarkdownable()) {
+ if ($this->parser->isBlockElement && $this->parser->isStartTag && !$this->lastWasBlockTag && !empty($this->output)) {
+ if (!empty($this->buffer)) {
+ $str =& $this->buffer[count($this->buffer) - 1];
+ } else {
+ $str =& $this->output;
+ }
+ if (substr($str, -strlen($this->indent) - 1) != "\n" . $this->indent) {
+ $str .= "\n" . $this->indent;
+ }
+ }
+ $func = 'handleTag_' . $this->parser->tagName;
+ $this->$func();
+ if ($this->linkPosition == self::LINK_AFTER_PARAGRAPH && $this->parser->isBlockElement && !$this->parser->isStartTag && empty($this->parser->openTags)) {
+ $this->flushFootnotes();
+ }
+ if (!$this->parser->isStartTag) {
+ $this->lastClosedTag = $this->parser->tagName;
+ }
+ } else {
+ $this->handleTagToText();
+ $this->lastClosedTag = '';
+ }
+ break;
+ default:
+ trigger_error('invalid node type', E_USER_ERROR);
+ break;
+ }
+ $this->lastWasBlockTag = $this->parser->nodeType == 'tag' && $this->parser->isStartTag && $this->parser->isBlockElement;
+ }
+ if (!empty($this->buffer)) {
+ // trigger_error('buffer was not flushed, this is a bug. please report!', E_USER_WARNING);
+ while (!empty($this->buffer)) {
+ $this->out($this->unbuffer());
+ }
+ }
+ // cleanup
+ $this->output = rtrim(str_replace('&amp;', '&', str_replace('&lt;', '<', str_replace('&gt;', '>', $this->output))));
+ // end parsing, flush stacked tags
+ $this->flushFootnotes();
+ $this->stack = array();
+ }
+
+ /**
+ * check if current tag can be converted to Markdown
+ *
+ * @param void
+ * @return bool
+ */
+ protected function isMarkdownable()
+ {
+ if (!isset($this->isMarkdownable[$this->parser->tagName])) {
+ // simply not markdownable
+
+ return false;
+ }
+ if ($this->parser->isStartTag) {
+ $return = true;
+ if ($this->keepHTML) {
+ $diff = array_diff(array_keys($this->parser->tagAttributes), array_keys($this->isMarkdownable[$this->parser->tagName]));
+ if (!empty($diff)) {
+ // non markdownable attributes given
+ $return = false;
+ }
+ }
+ if ($return) {
+ foreach ($this->isMarkdownable[$this->parser->tagName] as $attr => $type) {
+ if ($type == 'required' && !isset($this->parser->tagAttributes[$attr])) {
+ // required markdown attribute not given
+ $return = false;
+ break;
+ }
+ }
+ }
+ if (!$return) {
+ array_push($this->notConverted, $this->parser->tagName . '::' . implode('/', $this->parser->openTags));
+ }
+
+ return $return;
+ } else {
+ if (!empty($this->notConverted) && end($this->notConverted) === $this->parser->tagName . '::' . implode('/', $this->parser->openTags)) {
+ array_pop($this->notConverted);
+
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ /**
+ * output footnotes
+ *
+ * @param void
+ * @return void
+ */
+ protected function flushFootnotes()
+ {
+ $out = false;
+ foreach ($this->footnotes as $k => $tag) {
+ if (!isset($tag['unstacked'])) {
+ if (!$out) {
+ $out = true;
+ $this->out("\n\n", true);
+ } else {
+ $this->out("\n", true);
+ }
+ $this->out(' [' . $tag['linkID'] . ']: ' . $this->getLinkReference($tag), true);
+ $tag['unstacked'] = true;
+ $this->footnotes[$k] = $tag;
+ }
+ }
+ }
+
+ /**
+ * return formated link reference
+ *
+ * @param array $tag
+ * @return string link reference
+ */
+ protected function getLinkReference($tag)
+ {
+ return $tag['href'] . (isset($tag['title']) ? ' "' . $tag['title'] . '"' : '');
+ }
+
+ /**
+ * flush enqued linebreaks
+ *
+ * @param void
+ * @return void
+ */
+ protected function flushLinebreaks()
+ {
+ if ($this->lineBreaks && !empty($this->output)) {
+ $this->out(str_repeat("\n" . $this->indent, $this->lineBreaks), true);
+ }
+ $this->lineBreaks = 0;
+ }
+
+ /**
+ * handle non Markdownable tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTagToText()
+ {
+ if (!$this->keepHTML) {
+ if (!$this->parser->isStartTag && $this->parser->isBlockElement) {
+ $this->setLineBreaks(2);
+ }
+ } else {
+ // dont convert to markdown inside this tag
+ /** TODO: markdown extra **/
+ if (!$this->parser->isEmptyTag) {
+ if ($this->parser->isStartTag) {
+ if (!$this->skipConversion) {
+ $this->skipConversion = $this->parser->tagName . '::' . implode('/', $this->parser->openTags);
+ }
+ } else {
+ if ($this->skipConversion == $this->parser->tagName . '::' . implode('/', $this->parser->openTags)) {
+ $this->skipConversion = false;
+ }
+ }
+ }
+
+ if ($this->parser->isBlockElement) {
+ if ($this->parser->isStartTag) {
+ // looks like ins or del are block elements now
+ if (in_array($this->parent(), array('ins', 'del'))) {
+ $this->out("\n", true);
+ $this->indent(' ');
+ }
+ // don't indent inside <pre> tags
+ if ($this->parser->tagName == 'pre') {
+ $this->out($this->parser->node);
+ static $indent;
+ $indent = $this->indent;
+ $this->indent = '';
+ } else {
+ $this->out($this->parser->node . "\n" . $this->indent);
+ if (!$this->parser->isEmptyTag) {
+ $this->indent(' ');
+ } else {
+ $this->setLineBreaks(1);
+ }
+ $this->parser->html = ltrim($this->parser->html);
+ }
+ } else {
+ if (!$this->parser->keepWhitespace) {
+ $this->output = rtrim($this->output);
+ }
+ if ($this->parser->tagName != 'pre') {
+ $this->indent(' ');
+ $this->out("\n" . $this->indent . $this->parser->node);
+ } else {
+ // reset indentation
+ $this->out($this->parser->node);
+ static $indent;
+ $this->indent = $indent;
+ }
+
+ if (in_array($this->parent(), array('ins', 'del'))) {
+ // ins or del was block element
+ $this->out("\n");
+ $this->indent(' ');
+ }
+ if ($this->parser->tagName == 'li') {
+ $this->setLineBreaks(1);
+ } else {
+ $this->setLineBreaks(2);
+ }
+ }
+ } else {
+ $this->out($this->parser->node);
+ }
+ if (in_array($this->parser->tagName, array('code', 'pre'))) {
+ if ($this->parser->isStartTag) {
+ $this->buffer();
+ } else {
+ // add stuff so cleanup just reverses this
+ $this->out(str_replace('&lt;', '&amp;lt;', str_replace('&gt;', '&amp;gt;', $this->unbuffer())));
+ }
+ }
+ }
+ }
+
+ /**
+ * handle plain text
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleText()
+ {
+ if ($this->hasParent('pre') && strpos($this->parser->node, "\n") !== false) {
+ $this->parser->node = str_replace("\n", "\n" . $this->indent, $this->parser->node);
+ }
+ if (!$this->hasParent('code') && !$this->hasParent('pre')) {
+ // entity decode
+ $this->parser->node = $this->decode($this->parser->node);
+ if (!$this->skipConversion) {
+ // escape some chars in normal Text
+ $this->parser->node = preg_replace($this->escapeInText['search'], $this->escapeInText['replace'], $this->parser->node);
+ }
+ } else {
+ $this->parser->node = str_replace(array('&quot;', '&apos'), array('"', '\''), $this->parser->node);
+ }
+ $this->out($this->parser->node);
+ $this->lastClosedTag = '';
+ }
+
+ /**
+ * handle <em> and <i> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_em()
+ {
+ $this->out('_', true);
+ }
+
+ protected function handleTag_i()
+ {
+ $this->handleTag_em();
+ }
+
+ /**
+ * handle <strong> and <b> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_strong()
+ {
+ $this->out('**', true);
+ }
+
+ protected function handleTag_b()
+ {
+ $this->handleTag_strong();
+ }
+
+ /**
+ * handle <h1> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h1()
+ {
+ $this->handleHeader(1);
+ }
+
+ /**
+ * handle <h2> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h2()
+ {
+ $this->handleHeader(2);
+ }
+
+ /**
+ * handle <h3> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h3()
+ {
+ $this->handleHeader(3);
+ }
+
+ /**
+ * handle <h4> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h4()
+ {
+ $this->handleHeader(4);
+ }
+
+ /**
+ * handle <h5> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h5()
+ {
+ $this->handleHeader(5);
+ }
+
+ /**
+ * handle <h6> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_h6()
+ {
+ $this->handleHeader(6);
+ }
+
+ /**
+ * handle header tags (<h1> - <h6>)
+ *
+ * @param int $level 1-6
+ * @return void
+ */
+ protected function handleHeader($level)
+ {
+ if ($this->parser->isStartTag) {
+ $this->out(str_repeat('#', $level) . ' ', true);
+ } else {
+ $this->setLineBreaks(2);
+ }
+ }
+
+ /**
+ * handle <p> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_p()
+ {
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(2);
+ }
+ }
+
+ /**
+ * handle <a> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_a()
+ {
+ if ($this->parser->isStartTag) {
+ $this->buffer();
+ $this->handleTag_a_parser();
+ $this->stack();
+ } else {
+ $tag = $this->unstack();
+ $buffer = $this->unbuffer();
+ $this->handleTag_a_converter($tag, $buffer);
+ $this->out($this->handleTag_a_converter($tag, $buffer), true);
+ }
+ }
+
+ /**
+ * handle <a> tags parsing
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_a_parser()
+ {
+ if (isset($this->parser->tagAttributes['title'])) {
+ $this->parser->tagAttributes['title'] = $this->decode($this->parser->tagAttributes['title']);
+ } else {
+ $this->parser->tagAttributes['title'] = null;
+ }
+ $this->parser->tagAttributes['href'] = $this->decode(trim($this->parser->tagAttributes['href']));
+ }
+
+ /**
+ * handle <a> tags conversion
+ *
+ * @param array $tag
+ * @param string $buffer
+ * @return string The markdownified link
+ */
+ protected function handleTag_a_converter($tag, $buffer)
+ {
+ if (empty($tag['href']) && empty($tag['title'])) {
+ // empty links... testcase mania, who would possibly do anything like that?!
+ return '[' . $buffer . ']()';
+ }
+
+ if ($buffer == $tag['href'] && empty($tag['title'])) {
+ // <http://example.com>
+ return '<' . $buffer . '>';
+ }
+
+ $bufferDecoded = $this->decode(trim($buffer));
+ if (substr($tag['href'], 0, 7) == 'mailto:' && 'mailto:' . $bufferDecoded == $tag['href']) {
+ if (is_null($tag['title'])) {
+ // <mail@example.com>
+ return '<' . $bufferDecoded . '>';
+ }
+ // [mail@example.com][1]
+ // ...
+ // [1]: mailto:mail@example.com Title
+ $tag['href'] = 'mailto:' . $bufferDecoded;
+ }
+
+ if ($this->linkPosition == self::LINK_IN_PARAGRAPH) {
+ return '[' . $buffer . '](' . $this->getLinkReference($tag) . ')';
+ }
+
+ // [This link][id]
+ foreach ($this->footnotes as $tag2) {
+ if ($tag2['href'] == $tag['href'] && $tag2['title'] === $tag['title']) {
+ $tag['linkID'] = $tag2['linkID'];
+ break;
+ }
+ }
+ if (!isset($tag['linkID'])) {
+ $tag['linkID'] = count($this->footnotes) + 1;
+ array_push($this->footnotes, $tag);
+ }
+
+ return '[' . $buffer . '][' . $tag['linkID'] . ']';
+ }
+
+ /**
+ * handle <img /> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_img()
+ {
+ if (!$this->parser->isStartTag) {
+ return; // just to be sure this is really an empty tag...
+ }
+
+ if (isset($this->parser->tagAttributes['title'])) {
+ $this->parser->tagAttributes['title'] = $this->decode($this->parser->tagAttributes['title']);
+ } else {
+ $this->parser->tagAttributes['title'] = null;
+ }
+ if (isset($this->parser->tagAttributes['alt'])) {
+ $this->parser->tagAttributes['alt'] = $this->decode($this->parser->tagAttributes['alt']);
+ } else {
+ $this->parser->tagAttributes['alt'] = null;
+ }
+
+ if (empty($this->parser->tagAttributes['src'])) {
+ // support for "empty" images... dunno if this is really needed
+ // but there are some test cases which do that...
+ if (!empty($this->parser->tagAttributes['title'])) {
+ $this->parser->tagAttributes['title'] = ' ' . $this->parser->tagAttributes['title'] . ' ';
+ }
+ $this->out('![' . $this->parser->tagAttributes['alt'] . '](' . $this->parser->tagAttributes['title'] . ')', true);
+
+ return;
+ } else {
+ $this->parser->tagAttributes['src'] = $this->decode($this->parser->tagAttributes['src']);
+ }
+
+ $out = '![' . $this->parser->tagAttributes['alt'] . ']';
+ if ($this->linkPosition == self::LINK_IN_PARAGRAPH) {
+ $out .= '(' . $this->parser->tagAttributes['src'];
+ if ($this->parser->tagAttributes['title']) {
+ $out .= ' "' . $this->parser->tagAttributes['title'] . '"';
+ }
+ $out .= ')';
+ $this->out($out, true);
+ return;
+ }
+
+ // ![This image][id]
+ $link_id = false;
+ if (!empty($this->footnotes)) {
+ foreach ($this->footnotes as $tag) {
+ if ($tag['href'] == $this->parser->tagAttributes['src']
+ && $tag['title'] === $this->parser->tagAttributes['title']
+ ) {
+ $link_id = $tag['linkID'];
+ break;
+ }
+ }
+ }
+ if (!$link_id) {
+ $link_id = count($this->footnotes) + 1;
+ $tag = array(
+ 'href' => $this->parser->tagAttributes['src'],
+ 'linkID' => $link_id,
+ 'title' => $this->parser->tagAttributes['title']
+ );
+ array_push($this->footnotes, $tag);
+ }
+ $out .= '[' . $link_id . ']';
+
+ $this->out($out, true);
+ }
+
+ /**
+ * handle <code> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_code()
+ {
+ if ($this->hasParent('pre')) {
+ // ignore code blocks inside <pre>
+
+ return;
+ }
+ if ($this->parser->isStartTag) {
+ $this->buffer();
+ } else {
+ $buffer = $this->unbuffer();
+ // use as many backticks as needed
+ preg_match_all('#`+#', $buffer, $matches);
+ if (!empty($matches[0])) {
+ rsort($matches[0]);
+
+ $ticks = '`';
+ while (true) {
+ if (!in_array($ticks, $matches[0])) {
+ break;
+ }
+ $ticks .= '`';
+ }
+ } else {
+ $ticks = '`';
+ }
+ if ($buffer[0] == '`' || substr($buffer, -1) == '`') {
+ $buffer = ' ' . $buffer . ' ';
+ }
+ $this->out($ticks . $buffer . $ticks, true);
+ }
+ }
+
+ /**
+ * handle <pre> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_pre()
+ {
+ if ($this->keepHTML && $this->parser->isStartTag) {
+ // check if a simple <code> follows
+ if (!preg_match('#^\s*<code\s*>#Us', $this->parser->html)) {
+ // this is no standard markdown code block
+ $this->handleTagToText();
+
+ return;
+ }
+ }
+ $this->indent(' ');
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(2);
+ } else {
+ $this->parser->html = ltrim($this->parser->html);
+ }
+ }
+
+ /**
+ * handle <blockquote> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_blockquote()
+ {
+ $this->indent('> ');
+ }
+
+ /**
+ * handle <ul> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_ul()
+ {
+ if ($this->parser->isStartTag) {
+ $this->stack();
+ if (!$this->keepHTML && $this->lastClosedTag == $this->parser->tagName) {
+ $this->out("\n" . $this->indent . '<!-- -->' . "\n" . $this->indent . "\n" . $this->indent);
+ }
+ } else {
+ $this->unstack();
+ if ($this->parent() != 'li' || preg_match('#^\s*(</li\s*>\s*<li\s*>\s*)?<(p|blockquote)\s*>#sU', $this->parser->html)) {
+ // dont make Markdown add unneeded paragraphs
+ $this->setLineBreaks(2);
+ }
+ }
+ }
+
+ /**
+ * handle <ul> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_ol()
+ {
+ // same as above
+ $this->parser->tagAttributes['num'] = 0;
+ $this->handleTag_ul();
+ }
+
+ /**
+ * handle <li> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_li()
+ {
+ if ($this->parent() == 'ol') {
+ $parent =& $this->getStacked('ol');
+ if ($this->parser->isStartTag) {
+ $parent['num']++;
+ $this->out(str_repeat(' ', 3 - strlen($parent['num'])) . $parent['num'] . '. ', true);
+ }
+ } else {
+ if ($this->parser->isStartTag) {
+ $this->out(' * ', true);
+ }
+ }
+ $this->indent(' ', false);
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(1);
+ }
+ }
+
+ /**
+ * handle <hr /> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_hr()
+ {
+ if (!$this->parser->isStartTag) {
+ return; // just to be sure this really is an empty tag
+ }
+ $this->out('* * *', true);
+ $this->setLineBreaks(2);
+ }
+
+ /**
+ * handle <br /> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_br()
+ {
+ $this->out(" \n" . $this->indent, true);
+ $this->parser->html = ltrim($this->parser->html);
+ }
+
+ /**
+ * add current node to the stack
+ * this only stores the attributes
+ *
+ * @param void
+ * @return void
+ */
+ protected function stack()
+ {
+ if (!isset($this->stack[$this->parser->tagName])) {
+ $this->stack[$this->parser->tagName] = array();
+ }
+ array_push($this->stack[$this->parser->tagName], $this->parser->tagAttributes);
+ }
+
+ /**
+ * remove current tag from stack
+ *
+ * @param void
+ * @return array
+ */
+ protected function unstack()
+ {
+ if (!isset($this->stack[$this->parser->tagName]) || !is_array($this->stack[$this->parser->tagName])) {
+ trigger_error('Trying to unstack from empty stack. This must not happen.', E_USER_ERROR);
+ }
+
+ return array_pop($this->stack[$this->parser->tagName]);
+ }
+
+ /**
+ * get last stacked element of type $tagName
+ *
+ * @param string $tagName
+ * @return array
+ */
+ protected function &getStacked($tagName)
+ {
+ // no end() so it can be referenced
+ return $this->stack[$tagName][count($this->stack[$tagName]) - 1];
+ }
+
+ /**
+ * set number of line breaks before next start tag
+ *
+ * @param int $number
+ * @return void
+ */
+ protected function setLineBreaks($number)
+ {
+ if ($this->lineBreaks < $number) {
+ $this->lineBreaks = $number;
+ }
+ }
+
+ /**
+ * buffer next parser output until unbuffer() is called
+ *
+ * @param void
+ * @return void
+ */
+ protected function buffer()
+ {
+ array_push($this->buffer, '');
+ }
+
+ /**
+ * end current buffer and return buffered output
+ *
+ * @param void
+ * @return string
+ */
+ protected function unbuffer()
+ {
+ return array_pop($this->buffer);
+ }
+
+ /**
+ * append string to the correct var, either
+ * directly to $this->output or to the current
+ * buffers
+ *
+ * @param string $put
+ * @param boolean $nowrap
+ * @return void
+ */
+ protected function out($put, $nowrap = false)
+ {
+ if (empty($put)) {
+ return;
+ }
+ if (!empty($this->buffer)) {
+ $this->buffer[count($this->buffer) - 1] .= $put;
+ } else {
+ if ($this->bodyWidth && !$this->parser->keepWhitespace) { // wrap lines
+ // get last line
+ $pos = strrpos($this->output, "\n");
+ if ($pos === false) {
+ $line = $this->output;
+ } else {
+ $line = substr($this->output, $pos);
+ }
+
+ if ($nowrap) {
+ if ($put[0] != "\n" && $this->strlen($line) + $this->strlen($put) > $this->bodyWidth) {
+ $this->output .= "\n" . $this->indent . $put;
+ } else {
+ $this->output .= $put;
+ }
+
+ return;
+ } else {
+ $put .= "\n"; // make sure we get all lines in the while below
+ $lineLen = $this->strlen($line);
+ while ($pos = strpos($put, "\n")) {
+ $putLine = substr($put, 0, $pos + 1);
+ $put = substr($put, $pos + 1);
+ $putLen = $this->strlen($putLine);
+ if ($lineLen + $putLen < $this->bodyWidth) {
+ $this->output .= $putLine;
+ $lineLen = $putLen;
+ } else {
+ $split = preg_split('#^(.{0,' . ($this->bodyWidth - $lineLen) . '})\b#', $putLine, 2, PREG_SPLIT_OFFSET_CAPTURE | PREG_SPLIT_DELIM_CAPTURE);
+ $this->output .= rtrim($split[1][0]) . "\n" . $this->indent . $this->wordwrap(ltrim($split[2][0]), $this->bodyWidth, "\n" . $this->indent, false);
+ }
+ }
+ $this->output = substr($this->output, 0, -1);
+
+ return;
+ }
+ } else {
+ $this->output .= $put;
+ }
+ }
+ }
+
+ /**
+ * indent next output (start tag) or unindent (end tag)
+ *
+ * @param string $str indentation
+ * @param bool $output add indendation to output
+ * @return void
+ */
+ protected function indent($str, $output = true)
+ {
+ if ($this->parser->isStartTag) {
+ $this->indent .= $str;
+ if ($output) {
+ $this->out($str, true);
+ }
+ } else {
+ $this->indent = substr($this->indent, 0, -strlen($str));
+ }
+ }
+
+ /**
+ * decode email addresses
+ *
+ * @author derernst@gmx.ch <http://www.php.net/manual/en/function.html-entity-decode.php#68536>
+ * @author Milian Wolff <http://milianw.de>
+ */
+ protected function decode($text, $quote_style = ENT_QUOTES)
+ {
+ return htmlspecialchars_decode($text, $quote_style);
+ }
+
+ /**
+ * callback for decode() which converts a hexadecimal entity to UTF-8
+ *
+ * @param array $matches
+ * @return string UTF-8 encoded
+ */
+ protected function _decode_hex($matches)
+ {
+ return $this->unichr(hexdec($matches[1]));
+ }
+
+ /**
+ * callback for decode() which converts a numerical entity to UTF-8
+ *
+ * @param array $matches
+ * @return string UTF-8 encoded
+ */
+ protected function _decode_numeric($matches)
+ {
+ return $this->unichr($matches[1]);
+ }
+
+ /**
+ * UTF-8 chr() which supports numeric entities
+ *
+ * @author grey - greywyvern - com <http://www.php.net/manual/en/function.chr.php#55978>
+ * @param array $matches
+ * @return string UTF-8 encoded
+ */
+ protected function unichr($dec)
+ {
+ if ($dec < 128) {
+ $utf = chr($dec);
+ } elseif ($dec < 2048) {
+ $utf = chr(192 + (($dec - ($dec % 64)) / 64));
+ $utf .= chr(128 + ($dec % 64));
+ } else {
+ $utf = chr(224 + (($dec - ($dec % 4096)) / 4096));
+ $utf .= chr(128 + ((($dec % 4096) - ($dec % 64)) / 64));
+ $utf .= chr(128 + ($dec % 64));
+ }
+
+ return $utf;
+ }
+
+ /**
+ * UTF-8 strlen()
+ *
+ * @param string $str
+ * @return int
+ *
+ * @author dtorop 932 at hotmail dot com <http://www.php.net/manual/en/function.strlen.php#37975>
+ * @author Milian Wolff <http://milianw.de>
+ */
+ protected function strlen($str)
+ {
+ if (function_exists('mb_strlen')) {
+ return mb_strlen($str, 'UTF-8');
+ } else {
+ return preg_match_all('/[\x00-\x7F\xC0-\xFD]/', $str, $var_empty);
+ }
+ }
+
+ /**
+ * wordwrap for utf8 encoded strings
+ *
+ * @param string $str
+ * @param integer $len
+ * @param string $what
+ * @return string
+ */
+ protected function wordwrap($str, $width, $break, $cut = false)
+ {
+ if (!$cut) {
+ $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){1,' . $width . '}\b#';
+ } else {
+ $regexp = '#^(?:[\x00-\x7F]|[\xC0-\xFF][\x80-\xBF]+){' . $width . '}#';
+ }
+ $return = '';
+ while (preg_match($regexp, $str, $matches)) {
+ $string = $matches[0];
+ $str = ltrim(substr($str, strlen($string)));
+ if (!$cut && isset($str[0]) && in_array($str[0], array('.', '!', ';', ':', '?', ','))) {
+ $string .= $str[0];
+ $str = ltrim(substr($str, 1));
+ }
+ $return .= $string . $break;
+ }
+
+ return $return . ltrim($str);
+ }
+
+ /**
+ * check if current node has a $tagName as parent (somewhere, not only the direct parent)
+ *
+ * @param string $tagName
+ * @return bool
+ */
+ protected function hasParent($tagName)
+ {
+ return in_array($tagName, $this->parser->openTags);
+ }
+
+ /**
+ * get tagName of direct parent tag
+ *
+ * @param void
+ * @return string $tagName
+ */
+ protected function parent()
+ {
+ return end($this->parser->openTags);
+ }
+
+ /**
+ * Trims whitespace in block-level elements, on the left side.
+ */
+ protected function fixBlockElementSpacing()
+ {
+ if ($this->parser->isStartTag) {
+ $this->parser->html = ltrim($this->parser->html);
+ }
+ }
+
+ /**
+ * Moves leading/trailing whitespace from inline elements outside of the
+ * element. This is to fix cases like `<strong> Text</strong>`, which if
+ * converted to `** strong**` would be incorrect Markdown.
+ *
+ * Examples:
+ *
+ * * leading: `<strong> Text</strong>` becomes ` <strong>Text</strong>`
+ * * trailing: `<strong>Text </strong>` becomes `<strong>Text</strong> `
+ */
+ protected function fixInlineElementSpacing()
+ {
+ if ($this->parser->isStartTag) {
+ // move spaces after the start element to before the element
+ if (preg_match('~^(\s+)~', $this->parser->html, $matches)) {
+ $this->out($matches[1]);
+ $this->parser->html = ltrim($this->parser->html, " \t\0\x0B");
+ }
+ } else {
+ if (!empty($this->buffer)) {
+ $str =& $this->buffer[count($this->buffer) - 1];
+ } else {
+ $str =& $this->output;
+ }
+
+ // move spaces before the end element to after the element
+ if (preg_match('~(\s+)$~', $str, $matches)) {
+ $str = rtrim($str, " \t\0\x0B");
+ $this->parser->html = $matches[1] . $this->parser->html;
+ }
+ }
+ }
+
+ /**
+ * Resetting the state forces the instance to behave as a fresh instance.
+ * Ideal for running within a loop where you want to maintain a single instance.
+ */
+ protected function resetState()
+ {
+ $this->notConverted = array();
+ $this->skipConversion = false;
+ $this->buffer = array();
+ $this->indent = '';
+ $this->stack = array();
+ $this->lineBreaks = 0;
+ $this->lastClosedTag = '';
+ $this->lastWasBlockTag = false;
+ $this->footnotes = array();
+ }
+}
diff --git a/vendor/pixel418/markdownify/src/ConverterExtra.php b/vendor/pixel418/markdownify/src/ConverterExtra.php
new file mode 100644
index 000000000..733955448
--- /dev/null
+++ b/vendor/pixel418/markdownify/src/ConverterExtra.php
@@ -0,0 +1,573 @@
+<?php
+
+/* This file is part of the Markdownify project, which is under LGPL license */
+
+namespace Markdownify;
+
+class ConverterExtra extends Converter
+{
+
+ /**
+ * table data, including rows with content and the maximum width of each col
+ *
+ * @var array
+ */
+ protected $table = array();
+
+ /**
+ * current col
+ *
+ * @var int
+ */
+ protected $col = -1;
+
+ /**
+ * current row
+ *
+ * @var int
+ */
+ protected $row = 0;
+
+ /**
+ * constructor, see Markdownify::Markdownify() for more information
+ */
+ public function __construct($linksAfterEachParagraph = self::LINK_AFTER_CONTENT, $bodyWidth = MDFY_BODYWIDTH, $keepHTML = MDFY_KEEPHTML)
+ {
+ parent::__construct($linksAfterEachParagraph, $bodyWidth, $keepHTML);
+
+ // new markdownable tags & attributes
+ // header ids: # foo {bar}
+ $this->isMarkdownable['h1']['id'] = 'optional';
+ $this->isMarkdownable['h1']['class'] = 'optional';
+ $this->isMarkdownable['h2']['id'] = 'optional';
+ $this->isMarkdownable['h2']['class'] = 'optional';
+ $this->isMarkdownable['h3']['id'] = 'optional';
+ $this->isMarkdownable['h3']['class'] = 'optional';
+ $this->isMarkdownable['h4']['id'] = 'optional';
+ $this->isMarkdownable['h4']['class'] = 'optional';
+ $this->isMarkdownable['h5']['id'] = 'optional';
+ $this->isMarkdownable['h5']['class'] = 'optional';
+ $this->isMarkdownable['h6']['id'] = 'optional';
+ $this->isMarkdownable['h6']['class'] = 'optional';
+ // tables
+ $this->isMarkdownable['table'] = array();
+ $this->isMarkdownable['th'] = array(
+ 'align' => 'optional',
+ );
+ $this->isMarkdownable['td'] = array(
+ 'align' => 'optional',
+ );
+ $this->isMarkdownable['tr'] = array();
+ array_push($this->ignore, 'thead');
+ array_push($this->ignore, 'tbody');
+ array_push($this->ignore, 'tfoot');
+ // definition lists
+ $this->isMarkdownable['dl'] = array();
+ $this->isMarkdownable['dd'] = array();
+ $this->isMarkdownable['dt'] = array();
+ // link class
+ $this->isMarkdownable['a']['id'] = 'optional';
+ $this->isMarkdownable['a']['class'] = 'optional';
+ // footnotes
+ $this->isMarkdownable['fnref'] = array(
+ 'target' => 'required',
+ );
+ $this->isMarkdownable['footnotes'] = array();
+ $this->isMarkdownable['fn'] = array(
+ 'name' => 'required',
+ );
+ $this->parser->blockElements['fnref'] = false;
+ $this->parser->blockElements['fn'] = true;
+ $this->parser->blockElements['footnotes'] = true;
+ // abbr
+ $this->isMarkdownable['abbr'] = array(
+ 'title' => 'required',
+ );
+ // build RegEx lookahead to decide wether table can pe parsed or not
+ $inlineTags = array_keys($this->parser->blockElements, false);
+ $colContents = '(?:[^<]|<(?:' . implode('|', $inlineTags) . '|[^a-z]))*';
+ $this->tableLookaheadHeader = '{
+ ^\s*(?:<thead\s*>)?\s* # open optional thead
+ <tr\s*>\s*(?: # start required row with headers
+ <th(?:\s+align=("|\')(?:left|center|right)\1)?\s*> # header with optional align
+ \s*' . $colContents . '\s* # contents
+ </th>\s* # close header
+ )+</tr> # close row with headers
+ \s*(?:</thead>)? # close optional thead
+ }sxi';
+ $this->tdSubstitute = '\s*' . $colContents . '\s* # contents
+ </td>\s*';
+ $this->tableLookaheadBody = '{
+ \s*(?:<tbody\s*>)?\s* # open optional tbody
+ (?:<tr\s*>\s* # start row
+ %s # cols to be substituted
+ </tr>)+ # close row
+ \s*(?:</tbody>)? # close optional tbody
+ \s*</table> # close table
+ }sxi';
+ }
+
+ /**
+ * handle header tags (<h1> - <h6>)
+ *
+ * @param int $level 1-6
+ * @return void
+ */
+ protected function handleHeader($level)
+ {
+ if ($this->parser->isStartTag) {
+ $this->parser->tagAttributes['cssSelector'] = $this->getCurrentCssSelector();
+ $this->stack();
+ } else {
+ $tag = $this->unstack();
+ if (!empty($tag['cssSelector'])) {
+ // {#id.class}
+ $this->out(' {' . $tag['cssSelector'] . '}');
+ }
+ }
+ parent::handleHeader($level);
+ }
+
+ /**
+ * handle <a> tags parsing
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_a_parser()
+ {
+ parent::handleTag_a_parser();
+ $this->parser->tagAttributes['cssSelector'] = $this->getCurrentCssSelector();
+ }
+
+ /**
+ * handle <a> tags conversion
+ *
+ * @param array $tag
+ * @param string $buffer
+ * @return string The markdownified link
+ */
+ protected function handleTag_a_converter($tag, $buffer)
+ {
+ $output = parent::handleTag_a_converter($tag, $buffer);
+ if (!empty($tag['cssSelector'])) {
+ // [This link][id]{#id.class}
+ $output .= '{' . $tag['cssSelector'] . '}';
+ }
+
+ return $output;
+ }
+
+ /**
+ * handle <abbr> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_abbr()
+ {
+ if ($this->parser->isStartTag) {
+ $this->stack();
+ $this->buffer();
+ } else {
+ $tag = $this->unstack();
+ $tag['text'] = $this->unbuffer();
+ $add = true;
+ foreach ($this->stack['abbr'] as $stacked) {
+ if ($stacked['text'] == $tag['text']) {
+ /** TODO: differing abbr definitions, i.e. different titles for same text **/
+ $add = false;
+ break;
+ }
+ }
+ $this->out($tag['text']);
+ if ($add) {
+ array_push($this->stack['abbr'], $tag);
+ }
+ }
+ }
+
+ /**
+ * flush stacked abbr tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function flushStacked_abbr()
+ {
+ $out = array();
+ foreach ($this->stack['abbr'] as $k => $tag) {
+ if (!isset($tag['unstacked'])) {
+ array_push($out, ' *[' . $tag['text'] . ']: ' . $tag['title']);
+ $tag['unstacked'] = true;
+ $this->stack['abbr'][$k] = $tag;
+ }
+ }
+ if (!empty($out)) {
+ $this->out("\n\n" . implode("\n", $out));
+ }
+ }
+
+ /**
+ * handle <table> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_table()
+ {
+ if ($this->parser->isStartTag) {
+ // check if upcoming table can be converted
+ if ($this->keepHTML) {
+ if (preg_match($this->tableLookaheadHeader, $this->parser->html, $matches)) {
+ // header seems good, now check body
+ // get align & number of cols
+ preg_match_all('#<th(?:\s+align=("|\')(left|right|center)\1)?\s*>#si', $matches[0], $cols);
+ $regEx = '';
+ $i = 1;
+ $aligns = array();
+ foreach ($cols[2] as $align) {
+ $align = strtolower($align);
+ array_push($aligns, $align);
+ if (empty($align)) {
+ $align = 'left'; // default value
+ }
+ $td = '\s+align=("|\')' . $align . '\\' . $i;
+ $i++;
+ if ($align == 'left') {
+ // look for empty align or left
+ $td = '(?:' . $td . ')?';
+ }
+ $td = '<td' . $td . '\s*>';
+ $regEx .= $td . $this->tdSubstitute;
+ }
+ $regEx = sprintf($this->tableLookaheadBody, $regEx);
+ if (preg_match($regEx, $this->parser->html, $matches, null, strlen($matches[0]))) {
+ // this is a markdownable table tag!
+ $this->table = array(
+ 'rows' => array(),
+ 'col_widths' => array(),
+ 'aligns' => $aligns,
+ );
+ $this->row = 0;
+ } else {
+ // non markdownable table
+ $this->handleTagToText();
+ }
+ } else {
+ // non markdownable table
+ $this->handleTagToText();
+ }
+ } else {
+ $this->table = array(
+ 'rows' => array(),
+ 'col_widths' => array(),
+ 'aligns' => array(),
+ );
+ $this->row = 0;
+ }
+ } else {
+ // finally build the table in Markdown Extra syntax
+ $separator = array();
+ if (!isset($this->table['aligns'])) {
+ $this->table['aligns'] = array();
+ }
+ // seperator with correct align identifiers
+ foreach ($this->table['aligns'] as $col => $align) {
+ if (!$this->keepHTML && !isset($this->table['col_widths'][$col])) {
+ break;
+ }
+ $left = ' ';
+ $right = ' ';
+ switch ($align) {
+ case 'left':
+ $left = ':';
+ break;
+ case 'center':
+ $right = ':';
+ $left = ':';
+ case 'right':
+ $right = ':';
+ break;
+ }
+ array_push($separator, $left . str_repeat('-', $this->table['col_widths'][$col]) . $right);
+ }
+ $separator = '|' . implode('|', $separator) . '|';
+
+ $rows = array();
+ // add padding
+ array_walk_recursive($this->table['rows'], array(&$this, 'alignTdContent'));
+ $header = array_shift($this->table['rows']);
+ array_push($rows, '| ' . implode(' | ', $header) . ' |');
+ array_push($rows, $separator);
+ foreach ($this->table['rows'] as $row) {
+ array_push($rows, '| ' . implode(' | ', $row) . ' |');
+ }
+ $this->out(implode("\n" . $this->indent, $rows));
+ $this->table = array();
+ $this->setLineBreaks(2);
+ }
+ }
+
+ /**
+ * properly pad content so it is aligned as whished
+ * should be used with array_walk_recursive on $this->table['rows']
+ *
+ * @param string &$content
+ * @param int $col
+ * @return void
+ */
+ protected function alignTdContent(&$content, $col)
+ {
+ if (!isset($this->table['aligns'][$col])) {
+ $this->table['aligns'][$col] = 'left';
+ }
+ switch ($this->table['aligns'][$col]) {
+ default:
+ case 'left':
+ $content .= str_repeat(' ', $this->table['col_widths'][$col] - $this->strlen($content));
+ break;
+ case 'right':
+ $content = str_repeat(' ', $this->table['col_widths'][$col] - $this->strlen($content)) . $content;
+ break;
+ case 'center':
+ $paddingNeeded = $this->table['col_widths'][$col] - $this->strlen($content);
+ $left = floor($paddingNeeded / 2);
+ $right = $paddingNeeded - $left;
+ $content = str_repeat(' ', $left) . $content . str_repeat(' ', $right);
+ break;
+ }
+ }
+
+ /**
+ * handle <tr> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_tr()
+ {
+ if ($this->parser->isStartTag) {
+ $this->col = -1;
+ } else {
+ $this->row++;
+ }
+ }
+
+ /**
+ * handle <td> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_td()
+ {
+ if ($this->parser->isStartTag) {
+ $this->col++;
+ if (!isset($this->table['col_widths'][$this->col])) {
+ $this->table['col_widths'][$this->col] = 0;
+ }
+ $this->buffer();
+ } else {
+ $buffer = trim($this->unbuffer());
+ if (!isset($this->table['col_widths'][$this->col])) {
+ $this->table['col_widths'][$this->col] = 0;
+ }
+ $this->table['col_widths'][$this->col] = max($this->table['col_widths'][$this->col], $this->strlen($buffer));
+ $this->table['rows'][$this->row][$this->col] = $buffer;
+ }
+ }
+
+ /**
+ * handle <th> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_th()
+ {
+ if (!$this->keepHTML && !isset($this->table['rows'][1]) && !isset($this->table['aligns'][$this->col + 1])) {
+ if (isset($this->parser->tagAttributes['align'])) {
+ $this->table['aligns'][$this->col + 1] = $this->parser->tagAttributes['align'];
+ } else {
+ $this->table['aligns'][$this->col + 1] = '';
+ }
+ }
+ $this->handleTag_td();
+ }
+
+ /**
+ * handle <dl> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_dl()
+ {
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(2);
+ }
+ }
+
+ /**
+ * handle <dt> tags
+ *
+ * @param void
+ * @return void
+ **/
+ protected function handleTag_dt()
+ {
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(1);
+ }
+ }
+
+ /**
+ * handle <dd> tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_dd()
+ {
+ if ($this->parser->isStartTag) {
+ if (substr(ltrim($this->parser->html), 0, 3) == '<p>') {
+ // next comes a paragraph, so we'll need an extra line
+ $this->out("\n" . $this->indent);
+ } elseif (substr($this->output, -2) == "\n\n") {
+ $this->output = substr($this->output, 0, -1);
+ }
+ $this->out(': ');
+ $this->indent(' ', false);
+ } else {
+ // lookahead for next dt
+ if (substr(ltrim($this->parser->html), 0, 4) == '<dt>') {
+ $this->setLineBreaks(2);
+ } else {
+ $this->setLineBreaks(1);
+ }
+ $this->indent(' ');
+ }
+ }
+
+ /**
+ * handle <fnref /> tags (custom footnote references, see markdownify_extra::parseString())
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_fnref()
+ {
+ $this->out('[^' . $this->parser->tagAttributes['target'] . ']');
+ }
+
+ /**
+ * handle <fn> tags (custom footnotes, see markdownify_extra::parseString()
+ * and markdownify_extra::_makeFootnotes())
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_fn()
+ {
+ if ($this->parser->isStartTag) {
+ $this->out('[^' . $this->parser->tagAttributes['name'] . ']:');
+ $this->setLineBreaks(1);
+ } else {
+ $this->setLineBreaks(2);
+ }
+ $this->indent(' ');
+ }
+
+ /**
+ * handle <footnotes> tag (custom footnotes, see markdownify_extra::parseString()
+ * and markdownify_extra::_makeFootnotes())
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleTag_footnotes()
+ {
+ if (!$this->parser->isStartTag) {
+ $this->setLineBreaks(2);
+ }
+ }
+
+ /**
+ * parse a HTML string, clean up footnotes prior
+ *
+ * @param string $HTML input
+ * @return string Markdown formatted output
+ */
+ public function parseString($html)
+ {
+ /** TODO: custom markdown-extra options, e.g. titles & classes **/
+ // <sup id="fnref:..."><a href"#fn..." rel="footnote">...</a></sup>
+ // => <fnref target="..." />
+ $html = preg_replace('@<sup id="fnref:([^"]+)">\s*<a href="#fn:\1" rel="footnote">\s*\d+\s*</a>\s*</sup>@Us', '<fnref target="$1" />', $html);
+ // <div class="footnotes">
+ // <hr />
+ // <ol>
+ //
+ // <li id="fn:...">...</li>
+ // ...
+ //
+ // </ol>
+ // </div>
+ // =>
+ // <footnotes>
+ // <fn name="...">...</fn>
+ // ...
+ // </footnotes>
+ $html = preg_replace_callback('#<div class="footnotes">\s*<hr />\s*<ol>\s*(.+)\s*</ol>\s*</div>#Us', array(&$this, '_makeFootnotes'), $html);
+
+ return parent::parseString($html);
+ }
+
+ /**
+ * replace HTML representation of footnotes with something more easily parsable
+ *
+ * @note this is a callback to be used in parseString()
+ *
+ * @param array $matches
+ * @return string
+ */
+ protected function _makeFootnotes($matches)
+ {
+ // <li id="fn:1">
+ // ...
+ // <a href="#fnref:block" rev="footnote">&#8617;</a></p>
+ // </li>
+ // => <fn name="1">...</fn>
+ // remove footnote link
+ $fns = preg_replace('@\s*(&#160;\s*)?<a href="#fnref:[^"]+" rev="footnote"[^>]*>&#8617;</a>\s*@s', '', $matches[1]);
+ // remove empty paragraph
+ $fns = preg_replace('@<p>\s*</p>@s', '', $fns);
+ // <li id="fn:1">...</li> -> <footnote nr="1">...</footnote>
+ $fns = str_replace('<li id="fn:', '<fn name="', $fns);
+
+ $fns = '<footnotes>' . $fns . '</footnotes>';
+
+ return preg_replace('#</li>\s*(?=(?:<fn|</footnotes>))#s', '</fn>$1', $fns);
+ }
+
+ /**
+ * handle <a> tags parsing
+ *
+ * @param void
+ * @return void
+ */
+ protected function getCurrentCssSelector()
+ {
+ $cssSelector = '';
+ if (isset($this->parser->tagAttributes['id'])) {
+ $cssSelector .= '#' . $this->decode($this->parser->tagAttributes['id']);
+ }
+ if (isset($this->parser->tagAttributes['class'])) {
+ $classes = explode(' ', $this->decode($this->parser->tagAttributes['class']));
+ $classes = array_filter($classes);
+ $cssSelector .= '.' . join('.', $classes);
+ }
+ return $cssSelector;
+ }
+}
diff --git a/vendor/pixel418/markdownify/src/Parser.php b/vendor/pixel418/markdownify/src/Parser.php
new file mode 100644
index 000000000..90fcdf9f8
--- /dev/null
+++ b/vendor/pixel418/markdownify/src/Parser.php
@@ -0,0 +1,564 @@
+<?php
+
+/* This file is part of the Markdownify project, which is under LGPL license */
+
+namespace Markdownify;
+
+class Parser
+{
+ public static $skipWhitespace = true;
+ public static $a_ord;
+ public static $z_ord;
+ public static $special_ords;
+
+ /**
+ * tags which are always empty (<br /> etc.)
+ *
+ * @var array<string>
+ */
+ public $emptyTags = array(
+ 'br',
+ 'hr',
+ 'input',
+ 'img',
+ 'area',
+ 'link',
+ 'meta',
+ 'param',
+ );
+
+ /**
+ * tags with preformatted text
+ * whitespaces wont be touched in them
+ *
+ * @var array<string>
+ */
+ public $preformattedTags = array(
+ 'script',
+ 'style',
+ 'pre',
+ 'code',
+ );
+
+ /**
+ * supress HTML tags inside preformatted tags (see above)
+ *
+ * @var bool
+ */
+ public $noTagsInCode = false;
+
+ /**
+ * html to be parsed
+ *
+ * @var string
+ */
+ public $html = '';
+
+ /**
+ * node type:
+ *
+ * - tag (see isStartTag)
+ * - text (includes cdata)
+ * - comment
+ * - doctype
+ * - pi (processing instruction)
+ *
+ * @var string
+ */
+ public $nodeType = '';
+
+ /**
+ * current node content, i.e. either a
+ * simple string (text node), or something like
+ * <tag attrib="value"...>
+ *
+ * @var string
+ */
+ public $node = '';
+
+ /**
+ * wether current node is an opening tag (<a>) or not (</a>)
+ * set to NULL if current node is not a tag
+ * NOTE: empty tags (<br />) set this to true as well!
+ *
+ * @var bool | null
+ */
+ public $isStartTag = null;
+
+ /**
+ * wether current node is an empty tag (<br />) or not (<a></a>)
+ *
+ * @var bool | null
+ */
+ public $isEmptyTag = null;
+
+ /**
+ * tag name
+ *
+ * @var string | null
+ */
+ public $tagName = '';
+
+ /**
+ * attributes of current tag
+ *
+ * @var array (attribName=>value) | null
+ */
+ public $tagAttributes = null;
+
+ /**
+ * whether or not the actual context is a inline context
+ *
+ * @var bool | null
+ */
+ public $isInlineContext = null;
+
+ /**
+ * whether the current tag is a block element
+ *
+ * @var bool | null
+ */
+ public $isBlockElement = null;
+
+ /**
+ * whether the previous tag (browser) is a block element
+ *
+ * @var bool | null
+ */
+ public $isNextToInlineContext = null;
+
+ /**
+ * keep whitespace
+ *
+ * @var int
+ */
+ public $keepWhitespace = 0;
+
+ /**
+ * list of open tags
+ * count this to get current depth
+ *
+ * @var array
+ */
+ public $openTags = array();
+
+ /**
+ * list of block elements
+ *
+ * @var array
+ * TODO: what shall we do with <del> and <ins> ?!
+ */
+ public $blockElements = array(
+ // tag name => <bool> is block
+ // block elements
+ 'address' => true,
+ 'blockquote' => true,
+ 'center' => true,
+ 'del' => true,
+ 'dir' => true,
+ 'div' => true,
+ 'dl' => true,
+ 'fieldset' => true,
+ 'form' => true,
+ 'h1' => true,
+ 'h2' => true,
+ 'h3' => true,
+ 'h4' => true,
+ 'h5' => true,
+ 'h6' => true,
+ 'hr' => true,
+ 'ins' => true,
+ 'isindex' => true,
+ 'menu' => true,
+ 'noframes' => true,
+ 'noscript' => true,
+ 'ol' => true,
+ 'p' => true,
+ 'pre' => true,
+ 'table' => true,
+ 'ul' => true,
+ // set table elements and list items to block as well
+ 'thead' => true,
+ 'tbody' => true,
+ 'tfoot' => true,
+ 'td' => true,
+ 'tr' => true,
+ 'th' => true,
+ 'li' => true,
+ 'dd' => true,
+ 'dt' => true,
+ // header items and html / body as well
+ 'html' => true,
+ 'body' => true,
+ 'head' => true,
+ 'meta' => true,
+ 'link' => true,
+ 'style' => true,
+ 'title' => true,
+ // unfancy media tags, when indented should be rendered as block
+ 'map' => true,
+ 'object' => true,
+ 'param' => true,
+ 'embed' => true,
+ 'area' => true,
+ // inline elements
+ 'a' => false,
+ 'abbr' => false,
+ 'acronym' => false,
+ 'applet' => false,
+ 'b' => false,
+ 'basefont' => false,
+ 'bdo' => false,
+ 'big' => false,
+ 'br' => false,
+ 'button' => false,
+ 'cite' => false,
+ 'code' => false,
+ 'del' => false,
+ 'dfn' => false,
+ 'em' => false,
+ 'font' => false,
+ 'i' => false,
+ 'img' => false,
+ 'ins' => false,
+ 'input' => false,
+ 'iframe' => false,
+ 'kbd' => false,
+ 'label' => false,
+ 'q' => false,
+ 'samp' => false,
+ 'script' => false,
+ 'select' => false,
+ 'small' => false,
+ 'span' => false,
+ 'strong' => false,
+ 'sub' => false,
+ 'sup' => false,
+ 'textarea' => false,
+ 'tt' => false,
+ 'var' => false,
+ );
+
+ /**
+ * get next node, set $this->html prior!
+ *
+ * @param void
+ * @return bool
+ */
+ public function nextNode()
+ {
+ if (empty($this->html)) {
+ // we are done with parsing the html string
+
+ return false;
+ }
+
+ if ($this->isStartTag && !$this->isEmptyTag) {
+ array_push($this->openTags, $this->tagName);
+ if (in_array($this->tagName, $this->preformattedTags)) {
+ // dont truncate whitespaces for <code> or <pre> contents
+ $this->keepWhitespace++;
+ }
+ }
+
+ if ($this->html[0] == '<') {
+ $token = substr($this->html, 0, 9);
+ if (substr($token, 0, 2) == '<?') {
+ // xml prolog or other pi's
+ /** TODO **/
+ // trigger_error('this might need some work', E_USER_NOTICE);
+ $pos = strpos($this->html, '>');
+ $this->setNode('pi', $pos + 1);
+
+ return true;
+ }
+ if (substr($token, 0, 4) == '<!--') {
+ // comment
+ $pos = strpos($this->html, '-->');
+ if ($pos === false) {
+ // could not find a closing -->, use next gt instead
+ // this is firefox' behaviour
+ $pos = strpos($this->html, '>') + 1;
+ } else {
+ $pos += 3;
+ }
+ $this->setNode('comment', $pos);
+
+ static::$skipWhitespace = true;
+
+ return true;
+ }
+ if ($token == '<!DOCTYPE') {
+ // doctype
+ $this->setNode('doctype', strpos($this->html, '>') + 1);
+
+ static::$skipWhitespace = true;
+
+ return true;
+ }
+ if ($token == '<![CDATA[') {
+ // cdata, use text node
+
+ // remove leading <![CDATA[
+ $this->html = substr($this->html, 9);
+
+ $this->setNode('text', strpos($this->html, ']]>') + 3);
+
+ // remove trailing ]]> and trim
+ $this->node = substr($this->node, 0, -3);
+ $this->handleWhitespaces();
+
+ static::$skipWhitespace = true;
+
+ return true;
+ }
+ if ($this->parseTag()) {
+ // seems to be a tag
+ // handle whitespaces
+ if ($this->isBlockElement) {
+ static::$skipWhitespace = true;
+ } else {
+ static::$skipWhitespace = false;
+ }
+
+ return true;
+ }
+ }
+ if ($this->keepWhitespace) {
+ static::$skipWhitespace = false;
+ }
+ // when we get here it seems to be a text node
+ $pos = strpos($this->html, '<');
+ if ($pos === false) {
+ $pos = strlen($this->html);
+ }
+ $this->setNode('text', $pos);
+ $this->handleWhitespaces();
+ if (static::$skipWhitespace && $this->node == ' ') {
+ return $this->nextNode();
+ }
+ $this->isInlineContext = true;
+ static::$skipWhitespace = false;
+
+ return true;
+ }
+
+ /**
+ * parse tag, set tag name and attributes, see if it's a closing tag and so forth...
+ *
+ * @param void
+ * @return bool
+ */
+ protected function parseTag()
+ {
+ if (!isset(static::$a_ord)) {
+ static::$a_ord = ord('a');
+ static::$z_ord = ord('z');
+ static::$special_ords = array(
+ ord(':'), // for xml:lang
+ ord('-'), // for http-equiv
+ );
+ }
+
+ $tagName = '';
+
+ $pos = 1;
+ $isStartTag = $this->html[$pos] != '/';
+ if (!$isStartTag) {
+ $pos++;
+ }
+ // get tagName
+ while (isset($this->html[$pos])) {
+ $pos_ord = ord(strtolower($this->html[$pos]));
+ if (($pos_ord >= static::$a_ord && $pos_ord <= static::$z_ord) || (!empty($tagName) && is_numeric($this->html[$pos]))) {
+ $tagName .= $this->html[$pos];
+ $pos++;
+ } else {
+ $pos--;
+ break;
+ }
+ }
+
+ $tagName = strtolower($tagName);
+ if (empty($tagName) || !isset($this->blockElements[$tagName])) {
+ // something went wrong => invalid tag
+ $this->invalidTag();
+
+ return false;
+ }
+ if ($this->noTagsInCode && end($this->openTags) == 'code' && !($tagName == 'code' && !$isStartTag)) {
+ // we supress all HTML tags inside code tags
+ $this->invalidTag();
+
+ return false;
+ }
+
+ // get tag attributes
+ /** TODO: in html 4 attributes do not need to be quoted **/
+ $isEmptyTag = false;
+ $attributes = array();
+ $currAttrib = '';
+ while (isset($this->html[$pos + 1])) {
+ $pos++;
+ // close tag
+ if ($this->html[$pos] == '>' || $this->html[$pos] . $this->html[$pos + 1] == '/>') {
+ if ($this->html[$pos] == '/') {
+ $isEmptyTag = true;
+ $pos++;
+ }
+ break;
+ }
+
+ $pos_ord = ord(strtolower($this->html[$pos]));
+ if (($pos_ord >= static::$a_ord && $pos_ord <= static::$z_ord) || in_array($pos_ord, static::$special_ords)) {
+ // attribute name
+ $currAttrib .= $this->html[$pos];
+ } elseif (in_array($this->html[$pos], array(' ', "\t", "\n"))) {
+ // drop whitespace
+ } elseif (in_array($this->html[$pos] . $this->html[$pos + 1], array('="', "='"))) {
+ // get attribute value
+ $pos++;
+ $await = $this->html[$pos]; // single or double quote
+ $pos++;
+ $value = '';
+ while (isset($this->html[$pos]) && $this->html[$pos] != $await) {
+ $value .= $this->html[$pos];
+ $pos++;
+ }
+ $attributes[$currAttrib] = $value;
+ $currAttrib = '';
+ } else {
+ $this->invalidTag();
+
+ return false;
+ }
+ }
+ if ($this->html[$pos] != '>') {
+ $this->invalidTag();
+
+ return false;
+ }
+
+ if (!empty($currAttrib)) {
+ // html 4 allows something like <option selected> instead of <option selected="selected">
+ $attributes[$currAttrib] = $currAttrib;
+ }
+ if (!$isStartTag) {
+ if (!empty($attributes) || $tagName != end($this->openTags)) {
+ // end tags must not contain any attributes
+ // or maybe we did not expect a different tag to be closed
+ $this->invalidTag();
+
+ return false;
+ }
+ array_pop($this->openTags);
+ if (in_array($tagName, $this->preformattedTags)) {
+ $this->keepWhitespace--;
+ }
+ }
+ $pos++;
+ $this->node = substr($this->html, 0, $pos);
+ $this->html = substr($this->html, $pos);
+ $this->tagName = $tagName;
+ $this->tagAttributes = $attributes;
+ $this->isStartTag = $isStartTag;
+ $this->isEmptyTag = $isEmptyTag || in_array($tagName, $this->emptyTags);
+ if ($this->isEmptyTag) {
+ // might be not well formed
+ $this->node = preg_replace('# */? *>$#', ' />', $this->node);
+ }
+ $this->nodeType = 'tag';
+ $this->isBlockElement = $this->blockElements[$tagName];
+ $this->isNextToInlineContext = $isStartTag && $this->isInlineContext;
+ $this->isInlineContext = !$this->isBlockElement;
+ return true;
+ }
+
+ /**
+ * handle invalid tags
+ *
+ * @param void
+ * @return void
+ */
+ protected function invalidTag()
+ {
+ $this->html = substr_replace($this->html, '&lt;', 0, 1);
+ }
+
+ /**
+ * update all vars and make $this->html shorter
+ *
+ * @param string $type see description for $this->nodeType
+ * @param int $pos to which position shall we cut?
+ * @return void
+ */
+ protected function setNode($type, $pos)
+ {
+ if ($this->nodeType == 'tag') {
+ // set tag specific vars to null
+ // $type == tag should not be called here
+ // see this::parseTag() for more
+ $this->tagName = null;
+ $this->tagAttributes = null;
+ $this->isStartTag = null;
+ $this->isEmptyTag = null;
+ $this->isBlockElement = null;
+
+ }
+ $this->nodeType = $type;
+ $this->node = substr($this->html, 0, $pos);
+ $this->html = substr($this->html, $pos);
+ }
+
+ /**
+ * check if $this->html begins with $str
+ *
+ * @param string $str
+ * @return bool
+ */
+ protected function match($str)
+ {
+ return substr($this->html, 0, strlen($str)) == $str;
+ }
+
+ /**
+ * truncate whitespaces
+ *
+ * @param void
+ * @return void
+ */
+ protected function handleWhitespaces()
+ {
+ if ($this->keepWhitespace) {
+ // <pre> or <code> before...
+
+ return;
+ }
+ // truncate multiple whitespaces to a single one
+ $this->node = preg_replace('#\s+#s', ' ', $this->node);
+ }
+
+ /**
+ * normalize self::node
+ *
+ * @param void
+ * @return void
+ */
+ protected function normalizeNode()
+ {
+ $this->node = '<';
+ if (!$this->isStartTag) {
+ $this->node .= '/' . $this->tagName . '>';
+
+ return;
+ }
+ $this->node .= $this->tagName;
+ foreach ($this->tagAttributes as $name => $value) {
+ $this->node .= ' ' . $name . '="' . str_replace('"', '&quot;', $value) . '"';
+ }
+ if ($this->isEmptyTag) {
+ $this->node .= ' /';
+ }
+ $this->node .= '>';
+ }
+}