From 3e28af10815d61d40b59d533a3ff6603a2f9ea82 Mon Sep 17 00:00:00 2001 From: funco Date: Fri, 26 Oct 2018 13:41:46 +0800 Subject: [PATCH] lx finish --- .gitignore | 2 + composer.json | 20 + composer.lock | 518 +++++++ src/Article/ArticleInterface.php | 52 + src/Article/Html/HtmlArticle.php | 105 ++ src/Article/Markdown/MarkdownArticle.php | 153 ++ src/Exception/MultiArticleException.php | 14 + src/Parser/Parser.php | 1780 ++++++++++++++++++++++ src/Publisher/Config.php | 121 ++ src/Publisher/Lx/LxPublisher.php | 140 ++ src/Publisher/Publisher.php | 44 + 11 files changed, 2949 insertions(+) create mode 100644 .gitignore create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 src/Article/ArticleInterface.php create mode 100644 src/Article/Html/HtmlArticle.php create mode 100644 src/Article/Markdown/MarkdownArticle.php create mode 100644 src/Exception/MultiArticleException.php create mode 100644 src/Parser/Parser.php create mode 100644 src/Publisher/Config.php create mode 100644 src/Publisher/Lx/LxPublisher.php create mode 100644 src/Publisher/Publisher.php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1457eb6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +test +vendor \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..cc58bd6 --- /dev/null +++ b/composer.json @@ -0,0 +1,20 @@ +{ + "name": "funco/multi-article", + "description": "Multi site article publishing tool", + "license": "Apache-2.0", + "authors": [ + { + "name": "funco", + "email": "linhongzhao321@hotmail.com" + } + ], + "autoload": { + "psr-4": { + "EFrame\\MultiArticle\\": "src", + "EFrame\\MultiArticle\\Publisher\\Lx\\": "src/Publisher/Lx" + } + }, + "require": { + "lexiangla/openapi": "dev-master" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..756a218 --- /dev/null +++ b/composer.lock @@ -0,0 +1,518 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "9e16fea71ee659150f81bfa60af1b810", + "packages": [ + { + "name": "guzzlehttp/guzzle", + "version": "6.3.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba", + "shasum": "" + }, + "require": { + "guzzlehttp/promises": "^1.0", + "guzzlehttp/psr7": "^1.4", + "php": ">=5.5" + }, + "require-dev": { + "ext-curl": "*", + "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0", + "psr/log": "^1.0" + }, + "suggest": { + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.3-dev" + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "homepage": "http://guzzlephp.org/", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "rest", + "web service" + ], + "time": "2018-04-22T15:46:56+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646", + "shasum": "" + }, + "require": { + "php": ">=5.5.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "time": "2016-12-20T10:07:11+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "1.4.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "shasum": "" + }, + "require": { + "php": ">=5.4.0", + "psr/http-message": "~1.0" + }, + "provide": { + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + }, + "files": [ + "src/functions_include.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Schultze", + "homepage": "https://github.com/Tobion" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "request", + "response", + "stream", + "uri", + "url" + ], + "time": "2017-03-20T17:10:46+00:00" + }, + { + "name": "lexiangla/openapi", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/moomnng/lxapi.git", + "reference": "d509b17334357f61ca839df6b1f773328713bd57" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/moomnng/lxapi/zipball/d509b17334357f61ca839df6b1f773328713bd57", + "reference": "d509b17334357f61ca839df6b1f773328713bd57", + "shasum": "" + }, + "require": { + "php": ">=5.5.0", + "php-http/guzzle6-adapter": "^1.0", + "woohoolabs/yang": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Lexiangla\\Openapi\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "authors": [ + { + "name": "Today", + "email": "todaychen@tencent.com" + } + ], + "description": "Tencent Lexiang OpenApi", + "homepage": "https://lexiangla.com", + "keywords": [ + "api", + "lexiang", + "lexiangla", + "open", + "tencent" + ], + "time": "2018-10-15T08:53:20+00:00" + }, + { + "name": "php-http/guzzle6-adapter", + "version": "v1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-http/guzzle6-adapter.git", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/guzzle6-adapter/zipball/a56941f9dc6110409cfcddc91546ee97039277ab", + "reference": "a56941f9dc6110409cfcddc91546ee97039277ab", + "shasum": "" + }, + "require": { + "guzzlehttp/guzzle": "^6.0", + "php": ">=5.5.0", + "php-http/httplug": "^1.0" + }, + "provide": { + "php-http/async-client-implementation": "1.0", + "php-http/client-implementation": "1.0" + }, + "require-dev": { + "ext-curl": "*", + "php-http/adapter-integration-tests": "^0.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Adapter\\Guzzle6\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "David de Boer", + "email": "david@ddeboer.nl" + } + ], + "description": "Guzzle 6 HTTP Adapter", + "homepage": "http://httplug.io", + "keywords": [ + "Guzzle", + "http" + ], + "time": "2016-05-10T06:13:32+00:00" + }, + { + "name": "php-http/httplug", + "version": "v1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/httplug.git", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/httplug/zipball/1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "reference": "1c6381726c18579c4ca2ef1ec1498fdae8bdf018", + "shasum": "" + }, + "require": { + "php": ">=5.4", + "php-http/promise": "^1.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eric GELOEN", + "email": "geloen.eric@gmail.com" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + } + ], + "description": "HTTPlug, the HTTP client abstraction for PHP", + "homepage": "http://httplug.io", + "keywords": [ + "client", + "http" + ], + "time": "2016-08-31T08:30:17+00:00" + }, + { + "name": "php-http/promise", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-http/promise.git", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-http/promise/zipball/dc494cdc9d7160b9a09bd5573272195242ce7980", + "reference": "dc494cdc9d7160b9a09bd5573272195242ce7980", + "shasum": "" + }, + "require-dev": { + "henrikbjorn/phpspec-code-coverage": "^1.0", + "phpspec/phpspec": "^2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Http\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com" + }, + { + "name": "Joel Wurtz", + "email": "joel.wurtz@gmail.com" + } + ], + "description": "Promise used for asynchronous HTTP requests", + "homepage": "http://httplug.io", + "keywords": [ + "promise" + ], + "time": "2016-01-26T13:27:02+00:00" + }, + { + "name": "psr/http-message", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", + "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "time": "2016-08-06T14:39:51+00:00" + }, + { + "name": "woohoolabs/yang", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/woohoolabs/yang.git", + "reference": "e2af383bd23fe9d92480fc17f73cce0b03678830" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/woohoolabs/yang/zipball/e2af383bd23fe9d92480fc17f73cce0b03678830", + "reference": "e2af383bd23fe9d92480fc17f73cce0b03678830", + "shasum": "" + }, + "require": { + "php": "^7.1.0", + "php-http/client-implementation": "^1.0.0", + "php-http/httplug": "^1.0.0", + "psr/http-message-implementation": "^1.0.0" + }, + "require-dev": { + "php-http/guzzle6-adapter": "^1.1.0", + "phpunit/phpunit": "^6.0.0", + "squizlabs/php_codesniffer": "^2.8.0" + }, + "suggest": { + "php-http/guzzle6-adapter": "Allows to use Guzzle 6 as the HTTP client implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "WoohooLabs\\Yang\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Máté Kocsis", + "email": "kocsismate@woohoolabs.com" + } + ], + "description": "Woohoo Labs. Yang", + "keywords": [ + "Woohoo Labs.", + "Yang", + "json api", + "psr-7" + ], + "time": "2017-12-24T09:25:14+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": { + "lexiangla/openapi": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/src/Article/ArticleInterface.php b/src/Article/ArticleInterface.php new file mode 100644 index 0000000..a16c978 --- /dev/null +++ b/src/Article/ArticleInterface.php @@ -0,0 +1,52 @@ +title; + } + + public function getContent(): string + { + return $this->content; + } + + public function getStatement(): string + { + return $this->statement; + } + + public function getAuthor(): string + { + return $this->author; + } + + public function getAuthorGroup(): string + { + return $this->authorGroup; + } + + public function getIntroduction(): string + { + return $this->intro; + } + + public function getCover(): string + { + return $this->cover; + } + + public function setTitle(string $title) + { + $this->title = $title; + } + + /** + * @param string $content + */ + public function setContent(string $content) + { + $this->content = trim($content); + } + + public function setStatement(string $statement) + { + $this->statement = $statement; + } + + public function setAuthor(string $author) + { + $this->author = $author; + } + + public function setAuthorGroup(string $group) + { + $this->authorGroup = $group; + } + + public function setIntroduction(string $intro) + { + $this->intro = $intro; + } + + public function setCover(string $cover) + { + $this->cover = $cover; + } + + public function toType(int $type): ArticleInterface + { + $result = false; + switch ($type) { + case ARTICLE_CONTENT_TYPE_TXT: + $result = $this->toTxt(); + break; + case ARTICLE_CONTENT_TYPE_HTML: + $result = $this->toHtml(); + break; + case ARTICLE_CONTENT_TYPE_MD: + $result = $this->toMarkdown(); + break; + } + return $result; + } + + protected function toMarkdown() + { + return $this; + } + + protected function toTxt() + { + + } + + protected function toHtml() + { + $parser = new Parser(); + $htmlArticle = new HtmlArticle(); + $htmlArticle->setContent($parser->makeHtml($this->content)); + return $htmlArticle; + } + + public function getKeys(): array + { + // TODO: Implement getKeys() method. + } + + public function setKeys(array $keys) + { + // TODO: Implement setKeys() method. + } + + public function addKey(string $key) + { + // TODO: Implement addKey() method. + } + +} \ No newline at end of file diff --git a/src/Exception/MultiArticleException.php b/src/Exception/MultiArticleException.php new file mode 100644 index 0000000..420343c --- /dev/null +++ b/src/Exception/MultiArticleException.php @@ -0,0 +1,14 @@ + 'table|tbody|thead|tfoot|tr|td|th' + ); + + /** + * _footnotes + * + * @var array + */ + public $_footnotes; + + /** + * @var bool + */ + public $_html = false; + + /** + * @var bool + */ + public $_line = false; + + /** + * @var array + */ + public $blockParsers = array( + array('code', 10), + array('shtml', 20), + array('pre', 30), + array('ahtml', 40), + array('list', 50), + array('math', 60), + array('html', 70), + array('footnote', 80), + array('definition', 90), + array('quote', 100), + array('table', 110), + array('sh', 120), + array('mh', 130), + array('hr', 140), + array('default', 9999) + ); + + /** + * _blocks + * + * @var array + */ + private $_blocks; + + /** + * _current + * + * @var string + */ + private $_current; + + /** + * _pos + * + * @var int + */ + private $_pos; + + /** + * _definitions + * + * @var array + */ + public $_definitions; + + /** + * @var array + */ + private $_hooks = array(); + + /** + * @var array + */ + private $_holders; + + /** + * @var string + */ + private $_uniqid; + + /** + * @var int + */ + private $_id; + + /** + * @var array + */ + private $_parsers = array(); + + /** + * makeHtml + * + * @param mixed $text + * @return string + */ + public function makeHtml($text) + { + $this->_footnotes = array(); + $this->_definitions = array(); + $this->_holders = array(); + $this->_uniqid = md5(uniqid()); + $this->_id = 0; + + usort($this->blockParsers, function ($a, $b) { + return $a[1] < $b[1] ? -1 : 1; + }); + + foreach ($this->blockParsers as $parser) { + list ($name) = $parser; + + if (isset($parser[2])) { + $this->_parsers[$name] = $parser[2]; + } else { + $this->_parsers[$name] = array($this, 'parseBlock' . ucfirst($name)); + } + } + + $text = $this->initText($text); + $html = $this->parse($text); + $html = $this->makeFootnotes($html); + $html = $this->optimizeLines($html); + + return $this->call('makeHtml', $html); + } + + /** + * @param $html + */ + public function enableHtml($html = true) + { + $this->_html = $html; + } + + /** + * @param bool $line + */ + public function enableLine($line = true) + { + $this->_line = $line; + } + + /** + * @param $type + * @param $callback + */ + public function hook($type, $callback) + { + $this->_hooks[$type][] = $callback; + } + + /** + * @param $str + * @return string + */ + public function makeHolder($str) + { + $key = "\r" . $this->_uniqid . $this->_id . "\r"; + $this->_id++; + $this->_holders[$key] = $str; + + return $key; + } + + /** + * @param $text + * @return mixed + */ + private function initText($text) + { + $text = str_replace(array("\t", "\r"), array(' ', ''), $text); + return $text; + } + + /** + * @param $html + * @return string + */ + private function makeFootnotes($html) + { + if (count($this->_footnotes) > 0) { + $html .= '

    '; + $index = 1; + + while ($val = array_shift($this->_footnotes)) { + if (is_string($val)) { + $val .= " "; + } else { + $val[count($val) - 1] .= " "; + $val = count($val) > 1 ? $this->parse(implode("\n", $val)) : $this->parseInline($val[0]); + } + + $html .= "
  1. {$val}
  2. "; + $index++; + } + + $html .= '
'; + } + + return $html; + } + + /** + * parse + * + * @param string $text + * @param bool $inline + * @param int $offset + * @return string + */ + private function parse($text, $inline = false, $offset = 0) + { + $blocks = $this->parseBlock($text, $lines); + $html = ''; + + // inline mode for single normal block + if ($inline && count($blocks) == 1 && $blocks[0][0] == 'normal') { + $blocks[0][3] = true; + } + + foreach ($blocks as $block) { + list ($type, $start, $end, $value) = $block; + $extract = array_slice($lines, $start, $end - $start + 1); + $method = 'parse' . ucfirst($type); + + $extract = $this->call('before' . ucfirst($method), $extract, $value); + $result = $this->{$method}($extract, $value, $start + $offset, $end + $offset); + $result = $this->call('after' . ucfirst($method), $result, $value); + + $html .= $result; + } + + return $html; + } + + /** + * @param $text + * @param $clearHolders + * @return string + */ + private function releaseHolder($text, $clearHolders = true) + { + $deep = 0; + while (strpos($text, "\r") !== false && $deep < 10) { + $text = str_replace(array_keys($this->_holders), array_values($this->_holders), $text); + $deep++; + } + + if ($clearHolders) { + $this->_holders = array(); + } + + return $text; + } + + /** + * @param $start + * @param int $end + * @return string + */ + public function markLine($start, $end = -1) + { + if ($this->_line) { + $end = $end < 0 ? $start : $end; + return ''; + } + + return ''; + } + + /** + * @param array $lines + * @param $start + * @return array + */ + public function markLines(array $lines, $start) + { + $i = -1; + $self = $this; + + return $this->_line ? array_map(function ($line) use ($self, $start, &$i) { + $i++; + return $self->markLine($start + $i) . $line; + }, $lines) : $lines; + } + + /** + * @param $html + * @return string + */ + public function optimizeLines($html) + { + $last = 0; + + return $this->_line ? + preg_replace_callback("/class=\"line\" data\-start=\"([0-9]+)\" data\-end=\"([0-9]+)\" (data\-id=\"{$this->_uniqid}\")/", + function ($matches) use (&$last) { + if ($matches[1] != $last) { + $replace = 'class="line" data-start="' . $last . '" data-start-original="' . $matches[1] . '" data-end="' . $matches[2] . '" ' . $matches[3]; + } else { + $replace = $matches[0]; + } + + $last = $matches[2] + 1; + return $replace; + }, $html) : $html; + } + + /** + * @param $type + * @param $value + * @return mixed + */ + public function call($type, $value) + { + if (empty($this->_hooks[$type])) { + return $value; + } + + $args = func_get_args(); + $args = array_slice($args, 1); + + foreach ($this->_hooks[$type] as $callback) { + $value = call_user_func_array($callback, $args); + $args[0] = $value; + } + + return $value; + } + + /** + * parseInline + * + * @param string $text + * @param string $whiteList + * @param bool $clearHolders + * @param bool $enableAutoLink + * @return string + */ + public function parseInline($text, $whiteList = '', $clearHolders = true, $enableAutoLink = true) + { + $self = $this; + $text = $this->call('beforeParseInline', $text); + + // code + $text = preg_replace_callback( + "/(^|[^\\\])(`+)(.+?)\\2/", + function ($matches) use ($self) { + return $matches[1] . $self->makeHolder( + '' . htmlspecialchars($matches[3]) . '' + ); + }, + $text + ); + + // mathjax + $text = preg_replace_callback( + "/(^|[^\\\])(\\$+)(.+?)\\2/", + function ($matches) use ($self) { + return $matches[1] . $self->makeHolder( + $matches[2] . htmlspecialchars($matches[3]) . $matches[2] + ); + }, + $text + ); + + // escape + $text = preg_replace_callback( + "/\\\(.)/u", + function ($matches) use ($self) { + $escaped = htmlspecialchars($matches[1]); + $escaped = str_replace('$', '$', $escaped); + return $self->makeHolder($escaped); + }, + $text + ); + + // link + $text = preg_replace_callback( + "/<(https?:\/\/.+)>/i", + function ($matches) use ($self) { + $url = $self->cleanUrl($matches[1]); + $link = $self->call('parseLink', $matches[1]); + + return $self->makeHolder( + "{$link}" + ); + }, + $text + ); + + // encode unsafe tags + $text = preg_replace_callback( + "/<(\/?)([a-z0-9-]+)(\s+[^>]*)?>/i", + function ($matches) use ($self, $whiteList) { + if ($self->_html || false !== stripos( + '|' . $self->_commonWhiteList . '|' . $whiteList . '|', '|' . $matches[2] . '|' + )) { + return $self->makeHolder($matches[0]); + } else { + return htmlspecialchars($matches[0]); + } + }, + $text + ); + + if ($this->_html) { + $text = preg_replace_callback("//", function ($matches) use ($self) { + return $self->makeHolder($matches[0]); + }, $text); + } + + $text = str_replace(array('<', '>'), array('<', '>'), $text); + + // footnote + $text = preg_replace_callback( + "/\[\^((?:[^\]]|\\\\\]|\\\\\[)+?)\]/", + function ($matches) use ($self) { + $id = array_search($matches[1], $self->_footnotes); + + if (false === $id) { + $id = count($self->_footnotes) + 1; + $self->_footnotes[$id] = $self->parseInline($matches[1], '', false); + } + + return $self->makeHolder( + "{$id}" + ); + }, + $text + ); + + // image + $text = preg_replace_callback( + "/!\[((?:[^\]]|\\\\\]|\\\\\[)*?)\]\(((?:[^\)]|\\\\\)|\\\\\()+?)\)/", + function ($matches) use ($self) { + $escaped = htmlspecialchars($self->escapeBracket($matches[1])); + $url = $self->escapeBracket($matches[2]); + $url = $self->cleanUrl($url); + return $self->makeHolder( + "\"{$escaped}\"" + ); + }, + $text + ); + + $text = preg_replace_callback( + "/!\[((?:[^\]]|\\\\\]|\\\\\[)*?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/", + function ($matches) use ($self) { + $escaped = htmlspecialchars($self->escapeBracket($matches[1])); + + $result = isset($self->_definitions[$matches[2]]) ? + "_definitions[$matches[2]]}\" alt=\"{$escaped}\" title=\"{$escaped}\">" + : $escaped; + + return $self->makeHolder($result); + }, + $text + ); + + // link + $text = preg_replace_callback( + "/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\(((?:[^\)]|\\\\\)|\\\\\()+?)\)/", + function ($matches) use ($self) { + $escaped = $self->parseInline( + $self->escapeBracket($matches[1]), '', false, false + ); + $url = $self->escapeBracket($matches[2]); + $url = $self->cleanUrl($url); + return $self->makeHolder("{$escaped}"); + }, + $text + ); + + $text = preg_replace_callback( + "/\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]\[((?:[^\]]|\\\\\]|\\\\\[)+?)\]/", + function ($matches) use ($self) { + $escaped = $self->parseInline( + $self->escapeBracket($matches[1]), '', false + ); + $result = isset($self->_definitions[$matches[2]]) ? + "_definitions[$matches[2]]}\">{$escaped}" + : $escaped; + + return $self->makeHolder($result); + }, + $text + ); + + // strong and em and some fuck + $text = $this->parseInlineCallback($text); + $text = preg_replace( + "/<([_a-z0-9-\.\+]+@[^@]+\.[a-z]{2,})>/i", + "\\1", + $text + ); + + // autolink url + if ($enableAutoLink) { + $text = preg_replace_callback( + "/(^|[^\"])((https?):[\p{L}_0-9-\.\/%#!@\?\+=~\|\,&\(\)]+)($|[^\"])/iu", + function ($matches) use ($self) { + $link = $self->call('parseLink', $matches[2]); + return "{$matches[1]}{$link}{$matches[4]}"; + }, + $text + ); + } + + $text = $this->call('afterParseInlineBeforeRelease', $text); + $text = $this->releaseHolder($text, $clearHolders); + + $text = $this->call('afterParseInline', $text); + + return $text; + } + + /** + * @param $text + * @return mixed + */ + public function parseInlineCallback($text) + { + $self = $this; + + $text = preg_replace_callback( + "/(\*{3})(.+?)\\1/", + function ($matches) use ($self) { + return '' . + $self->parseInlineCallback($matches[2]) . + ''; + }, + $text + ); + + $text = preg_replace_callback( + "/(\*{2})(.+?)\\1/", + function ($matches) use ($self) { + return '' . + $self->parseInlineCallback($matches[2]) . + ''; + }, + $text + ); + + $text = preg_replace_callback( + "/(\*)(.+?)\\1/", + function ($matches) use ($self) { + return '' . + $self->parseInlineCallback($matches[2]) . + ''; + }, + $text + ); + + $text = preg_replace_callback( + "/(\s+|^)(_{3})(.+?)\\2(\s+|$)/", + function ($matches) use ($self) { + return $matches[1] . '' . + $self->parseInlineCallback($matches[3]) . + '' . $matches[4]; + }, + $text + ); + + $text = preg_replace_callback( + "/(\s+|^)(_{2})(.+?)\\2(\s+|$)/", + function ($matches) use ($self) { + return $matches[1] . '' . + $self->parseInlineCallback($matches[3]) . + '' . $matches[4]; + }, + $text + ); + + $text = preg_replace_callback( + "/(\s+|^)(_)(.+?)\\2(\s+|$)/", + function ($matches) use ($self) { + return $matches[1] . '' . + $self->parseInlineCallback($matches[3]) . + '' . $matches[4]; + }, + $text + ); + + $text = preg_replace_callback( + "/(~{2})(.+?)\\1/", + function ($matches) use ($self) { + return '' . + $self->parseInlineCallback($matches[2]) . + ''; + }, + $text + ); + + return $text; + } + + /** + * parseBlock + * + * @param string $text + * @param array $lines + * @return array + */ + private function parseBlock($text, &$lines) + { + $lines = explode("\n", $text); + $this->_blocks = array(); + $this->_current = 'normal'; + $this->_pos = -1; + + $state = array( + 'special' => implode("|", array_keys($this->_specialWhiteList)), + 'empty' => 0, + 'html' => false + ); + + // analyze by line + foreach ($lines as $key => $line) { + $block = $this->getBlock(); + $args = array($block, $key, $line, &$state, $lines); + + if ($this->_current != 'normal') { + $pass = call_user_func_array($this->_parsers[$this->_current], $args); + + if (!$pass) { + continue; + } + } + + foreach ($this->_parsers as $name => $parser) { + if ($name != $this->_current) { + $pass = call_user_func_array($parser, $args); + + if (!$pass) { + break; + } + } + } + } + + return $this->optimizeBlocks($this->_blocks, $lines); + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockList($block, $key, $line, &$state) + { + if (preg_match("/^(\s*)((?:[0-9]+\.)|\-|\+|\*)\s+/i", $line, $matches)) { + $space = strlen($matches[1]); + $state['empty'] = 0; + + // opened + if ($this->isBlock('list')) { + $this->setBlock($key, $space); + } else { + $this->startBlock('list', $key, $space); + } + + return false; + } elseif ($this->isBlock('list') && !preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line)) { + if ($state['empty'] <= 1 + && preg_match("/^(\s+)/", $line, $matches) + && strlen($matches[1]) > $block[3]) { + + $state['empty'] = 0; + $this->setBlock($key); + return false; + } elseif (preg_match("/^(\s*)$/", $line) && $state['empty'] == 0) { + $state['empty']++; + $this->setBlock($key); + return false; + } + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockCode($block, $key, $line) + { + if (preg_match("/^(\s*)(~{3,}|`{3,})([^`~]*)$/i", $line, $matches)) { + if ($this->isBlock('code')) { + $isAfterList = $block[3][2]; + + if ($isAfterList) { + $this->combineBlock() + ->setBlock($key); + } else { + $this->setBlock($key) + ->endBlock(); + } + } else { + $isAfterList = false; + + if ($this->isBlock('list')) { + $space = $block[3]; + + $isAfterList = ($space > 0 && strlen($matches[1]) >= $space) + || strlen($matches[1]) > $space; + } + + $this->startBlock('code', $key, array( + $matches[1], $matches[3], $isAfterList + )); + } + + return false; + } elseif ($this->isBlock('code')) { + $this->setBlock($key); + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockShtml($block, $key, $line, &$state) + { + if ($this->_html) { + if (preg_match("/^(\s*)!!!(\s*)$/", $line, $matches)) { + if ($this->isBlock('shtml')) { + $this->setBlock($key)->endBlock(); + } else { + $this->startBlock('shtml', $key); + } + + return false; + } elseif ($this->isBlock('shtml')) { + $this->setBlock($key); + return false; + } + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockAhtml($block, $key, $line, &$state) + { + if ($this->_html) { + if (preg_match("/^\s*<({$this->_blockHtmlTags})(\s+[^>]*)?>/i", $line, $matches)) { + if ($this->isBlock('ahtml')) { + $this->setBlock($key); + return false; + } elseif (empty($matches[2]) || $matches[2] != '/') { + $this->startBlock('ahtml', $key); + preg_match_all("/<({$this->_blockHtmlTags})(\s+[^>]*)?>/i", $line, $allMatches); + $lastMatch = $allMatches[1][count($allMatches[0]) - 1]; + + if (strpos($line, "") !== false) { + $this->endBlock(); + } else { + $state['html'] = $lastMatch; + } + return false; + } + } elseif (!!$state['html'] && strpos($line, "") !== false) { + $this->setBlock($key)->endBlock(); + $state['html'] = false; + return false; + } elseif ($this->isBlock('ahtml')) { + $this->setBlock($key); + return false; + } elseif (preg_match("/^\s*\s*$/", $line, $matches)) { + $this->startBlock('ahtml', $key)->endBlock(); + return false; + } + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockMath($block, $key, $line) + { + if (preg_match("/^(\s*)\\$\\$(\s*)$/", $line, $matches)) { + if ($this->isBlock('math')) { + $this->setBlock($key)->endBlock(); + } else { + $this->startBlock('math', $key); + } + + return false; + } elseif ($this->isBlock('math')) { + $this->setBlock($key); + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockPre($block, $key, $line, &$state) + { + if (preg_match("/^ {4}/", $line)) { + if ($this->isBlock('pre')) { + $this->setBlock($key); + } else { + $this->startBlock('pre', $key); + } + + return false; + } elseif ($this->isBlock('pre') && preg_match("/^\s*$/", $line)) { + $this->setBlock($key); + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockHtml($block, $key, $line, &$state) + { + if (preg_match("/^\s*<({$state['special']})(\s+[^>]*)?>/i", $line, $matches)) { + $tag = strtolower($matches[1]); + if (!$this->isBlock('html', $tag) && !$this->isBlock('pre')) { + $this->startBlock('html', $key, $tag); + } + + return false; + } elseif (preg_match("/<\/({$state['special']})>\s*$/i", $line, $matches)) { + $tag = strtolower($matches[1]); + + if ($this->isBlock('html', $tag)) { + $this->setBlock($key) + ->endBlock(); + } + + return false; + } elseif ($this->isBlock('html')) { + $this->setBlock($key); + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockFootnote($block, $key, $line) + { + if (preg_match("/^\[\^((?:[^\]]|\\]|\\[)+?)\]:/", $line, $matches)) { + $space = strlen($matches[0]) - 1; + $this->startBlock('footnote', $key, array( + $space, $matches[1] + )); + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockDefinition($block, $key, $line) + { + if (preg_match("/^\s*\[((?:[^\]]|\\]|\\[)+?)\]:\s*(.+)$/", $line, $matches)) { + $this->_definitions[$matches[1]] = $this->cleanUrl($matches[2]); + $this->startBlock('definition', $key) + ->endBlock(); + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockQuote($block, $key, $line) + { + if (preg_match("/^(\s*)>/", $line, $matches)) { + if ($this->isBlock('list') && strlen($matches[1]) > 0) { + $this->setBlock($key); + } elseif ($this->isBlock('quote')) { + $this->setBlock($key); + } else { + $this->startBlock('quote', $key); + } + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @param $lines + * @return bool + */ + private function parseBlockTable($block, $key, $line, &$state, $lines) + { + if (preg_match("/^((?:(?:(?:\||\+)(?:[ :]*\-+[ :]*)(?:\||\+))|(?:(?:[ :]*\-+[ :]*)(?:\||\+)(?:[ :]*\-+[ :]*))|(?:(?:[ :]*\-+[ :]*)(?:\||\+))|(?:(?:\||\+)(?:[ :]*\-+[ :]*)))+)$/", $line, $matches)) { + if ($this->isBlock('table')) { + $block[3][0][] = $block[3][2]; + $block[3][2]++; + $this->setBlock($key, $block[3]); + } else { + $head = 0; + + if (empty($block) || + $block[0] != 'normal' || + preg_match("/^\s*$/", $lines[$block[2]])) { + $this->startBlock('table', $key); + } else { + $head = 1; + $this->backBlock(1, 'table'); + } + + if ($matches[1][0] == '|') { + $matches[1] = substr($matches[1], 1); + + if ($matches[1][strlen($matches[1]) - 1] == '|') { + $matches[1] = substr($matches[1], 0, -1); + } + } + + $rows = preg_split("/(\+|\|)/", $matches[1]); + $aligns = array(); + foreach ($rows as $row) { + $align = 'none'; + + if (preg_match("/^\s*(:?)\-+(:?)\s*$/", $row, $matches)) { + if (!empty($matches[1]) && !empty($matches[2])) { + $align = 'center'; + } elseif (!empty($matches[1])) { + $align = 'left'; + } elseif (!empty($matches[2])) { + $align = 'right'; + } + } + + $aligns[] = $align; + } + + $this->setBlock($key, array(array($head), $aligns, $head + 1)); + } + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockSh($block, $key, $line) + { + if (preg_match("/^(#+)(.*)$/", $line, $matches)) { + $num = min(strlen($matches[1]), 6); + $this->startBlock('sh', $key, $num) + ->endBlock(); + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @param $lines + * @return bool + */ + private function parseBlockMh($block, $key, $line, &$state, $lines) + { + if (preg_match("/^\s*((=|-){2,})\s*$/", $line, $matches) + && ($block && $block[0] == "normal" && !preg_match("/^\s*$/", $lines[$block[2]]))) { // check if last line isn't empty + if ($this->isBlock('normal')) { + $this->backBlock(1, 'mh', $matches[1][0] == '=' ? 1 : 2) + ->setBlock($key) + ->endBlock(); + } else { + $this->startBlock('normal', $key); + } + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @return bool + */ + private function parseBlockHr($block, $key, $line) + { + if (preg_match("/^[-\*]{3,}\s*$/", $line)) { + $this->startBlock('hr', $key) + ->endBlock(); + + return false; + } + + return true; + } + + /** + * @param $block + * @param $key + * @param $line + * @param $state + * @return bool + */ + private function parseBlockDefault($block, $key, $line, &$state) + { + if ($this->isBlock('footnote')) { + preg_match("/^(\s*)/", $line, $matches); + if (strlen($matches[1]) >= $block[3][0]) { + $this->setBlock($key); + } else { + $this->startBlock('normal', $key); + } + } elseif ($this->isBlock('table')) { + if (false !== strpos($line, '|')) { + $block[3][2]++; + $this->setBlock($key, $block[3]); + } else { + $this->startBlock('normal', $key); + } + } elseif ($this->isBlock('quote')) { + if (!preg_match("/^(\s*)$/", $line)) { // empty line + $this->setBlock($key); + } else { + $this->startBlock('normal', $key); + } + } else { + if (empty($block) || $block[0] != 'normal') { + $this->startBlock('normal', $key); + } else { + $this->setBlock($key); + } + } + + return true; + } + + /** + * @param array $blocks + * @param array $lines + * @return array + */ + private function optimizeBlocks(array $blocks, array $lines) + { + $blocks = $this->call('beforeOptimizeBlocks', $blocks, $lines); + + $key = 0; + while (isset($blocks[$key])) { + $moved = false; + + $block = &$blocks[$key]; + $prevBlock = isset($blocks[$key - 1]) ? $blocks[$key - 1] : null; + $nextBlock = isset($blocks[$key + 1]) ? $blocks[$key + 1] : null; + + list ($type, $from, $to) = $block; + + if ('pre' == $type) { + $isEmpty = array_reduce( + array_slice($lines, $block[1], $block[2] - $block[1] + 1), + function ($result, $line) { + return preg_match("/^\s*$/", $line) && $result; + }, + true + ); + + if ($isEmpty) { + $block[0] = $type = 'normal'; + } + } + + if ('normal' == $type) { + // combine two blocks + $types = array('list', 'quote'); + + if ($from == $to && preg_match("/^\s*$/", $lines[$from]) + && !empty($prevBlock) && !empty($nextBlock)) { + if ($prevBlock[0] == $nextBlock[0] && in_array($prevBlock[0], $types)) { + // combine 3 blocks + $blocks[$key - 1] = array( + $prevBlock[0], $prevBlock[1], $nextBlock[2], null + ); + array_splice($blocks, $key, 2); + + // do not move + $moved = true; + } + } + } + + if (!$moved) { + $key++; + } + } + + return $this->call('afterOptimizeBlocks', $blocks, $lines); + } + + /** + * parseCode + * + * @param array $lines + * @param array $parts + * @param int $start + * @return string + */ + private function parseCode(array $lines, array $parts, $start) + { + list ($blank, $lang) = $parts; + $lang = trim($lang); + $count = strlen($blank); + + if (!preg_match("/^[_a-z0-9-\+\#\:\.]+$/i", $lang)) { + $lang = null; + } else { + $parts = explode(':', $lang); + if (count($parts) > 1) { + list ($lang, $rel) = $parts; + $lang = trim($lang); + $rel = trim($rel); + } + } + + $isEmpty = true; + + $lines = array_map(function ($line) use ($count, &$isEmpty) { + $line = preg_replace("/^[ ]{{$count}}/", '', $line); + if ($isEmpty && !preg_match("/^\s*$/", $line)) { + $isEmpty = false; + } + + return htmlspecialchars($line); + }, array_slice($lines, 1, -1)); + $str = implode("\n", $this->markLines($lines, $start + 1)); + + return $isEmpty ? '' : + '
'
+            . $str . '
'; + } + + /** + * parsePre + * + * @param array $lines + * @param mixed $value + * @param int $start + * @return string + */ + private function parsePre(array $lines, $value, $start) + { + foreach ($lines as &$line) { + $line = htmlspecialchars(substr($line, 4)); + } + + $str = implode("\n", $this->markLines($lines, $start)); + return preg_match("/^\s*$/", $str) ? '' : '
' . $str . '
'; + } + + /** + * parseAhtml + * + * @param array $lines + * @param mixed $value + * @param int $start + * @return string + */ + private function parseAhtml(array $lines, $value, $start) + { + return trim(implode("\n", $this->markLines($lines, $start))); + } + + /** + * parseShtml + * + * @param array $lines + * @param mixed $value + * @param int $start + * @return string + */ + private function parseShtml(array $lines, $value, $start) + { + return trim(implode("\n", $this->markLines(array_slice($lines, 1, -1), $start + 1))); + } + + /** + * parseMath + * + * @param array $lines + * @param mixed $value + * @param int $start + * @param int $end + * @return string + */ + private function parseMath(array $lines, $value, $start, $end) + { + return '

' . $this->markLine($start, $end) . htmlspecialchars(implode("\n", $lines)) . '

'; + } + + /** + * parseSh + * + * @param array $lines + * @param int $num + * @param int $start + * @param int $end + * @return string + */ + private function parseSh(array $lines, $num, $start, $end) + { + $line = $this->markLine($start, $end) . $this->parseInline(trim($lines[0], '# ')); + return preg_match("/^\s*$/", $line) ? '' : "{$line}"; + } + + /** + * parseMh + * + * @param array $lines + * @param int $num + * @param int $start + * @param int $end + * @return string + */ + private function parseMh(array $lines, $num, $start, $end) + { + return $this->parseSh($lines, $num, $start, $end); + } + + /** + * parseQuote + * + * @param array $lines + * @param mixed $value + * @param int $start + * @return string + */ + private function parseQuote(array $lines, $value, $start) + { + foreach ($lines as &$line) { + $line = preg_replace("/^\s*> ?/", '', $line); + } + $str = implode("\n", $lines); + + return preg_match("/^\s*$/", $str) ? '' : '
' . $this->parse($str, true, $start) . '
'; + } + + /** + * parseList + * + * @param array $lines + * @param mixed $value + * @param int $start + * @return string + */ + private function parseList(array $lines, $value, $start) + { + $html = ''; + $minSpace = 99999; + $secondMinSpace = 99999; + $found = false; + $secondFound = false; + $rows = array(); + + // count levels + foreach ($lines as $key => $line) { + if (preg_match("/^(\s*)((?:[0-9]+\.?)|\-|\+|\*)(\s+)(.*)$/i", $line, $matches)) { + $space = strlen($matches[1]); + $type = false !== strpos('+-*', $matches[2]) ? 'ul' : 'ol'; + $minSpace = min($space, $minSpace); + $found = true; + + if ($space > 0) { + $secondMinSpace = min($space, $secondMinSpace); + $secondFound = true; + } + + $rows[] = array($space, $type, $line, $matches[4]); + } else { + $rows[] = $line; + + if (preg_match("/^(\s*)/", $line, $matches)) { + $space = strlen($matches[1]); + + if ($space > 0) { + $secondMinSpace = min($space, $secondMinSpace); + $secondFound = true; + } + } + } + } + + $minSpace = $found ? $minSpace : 0; + $secondMinSpace = $secondFound ? $secondMinSpace : $minSpace; + + $lastType = ''; + $leftLines = array(); + $leftStart = 0; + + foreach ($rows as $key => $row) { + if (is_array($row)) { + list ($space, $type, $line, $text) = $row; + + if ($space != $minSpace) { + $leftLines[] = preg_replace("/^\s{" . $secondMinSpace . "}/", '', $line); + } else { + if (!empty($leftLines)) { + $html .= "
  • " . $this->parse(implode("\n", $leftLines), true, $start + $leftStart) . "
  • "; + } + + if ($lastType != $type) { + if (!empty($lastType)) { + $html .= ""; + } + + $html .= "<{$type}>"; + } + + $leftStart = $key; + $leftLines = array($text); + $lastType = $type; + } + } else { + $leftLines[] = preg_replace("/^\s{" . $secondMinSpace . "}/", '', $row); + } + } + + if (!empty($leftLines)) { + $html .= "
  • " . $this->parse(implode("\n", $leftLines), true, $start + $leftStart) . "
  • "; + } + + return $html; + } + + /** + * @param array $lines + * @param array $value + * @param int $start + * @return string + */ + private function parseTable(array $lines, array $value, $start) + { + list ($ignores, $aligns) = $value; + $head = count($ignores) > 0 && array_sum($ignores) > 0; + + $html = ''; + $body = $head ? null : true; + $output = false; + + foreach ($lines as $key => $line) { + if (in_array($key, $ignores)) { + if ($head && $output) { + $head = false; + $body = true; + } + + continue; + } + + $line = trim($line); + $output = true; + + if ($line[0] == '|') { + $line = substr($line, 1); + + if ($line[strlen($line) - 1] == '|') { + $line = substr($line, 0, -1); + } + } + + + $rows = array_map(function ($row) { + if (preg_match("/^\s*$/", $row)) { + return ' '; + } else { + return trim($row); + } + }, explode('|', $line)); + $columns = array(); + $last = -1; + + foreach ($rows as $row) { + if (strlen($row) > 0) { + $last++; + $columns[$last] = array( + isset($columns[$last]) ? $columns[$last][0] + 1 : 1, $row + ); + } elseif (isset($columns[$last])) { + $columns[$last][0]++; + } else { + $columns[0] = array(1, $row); + } + } + + if ($head) { + $html .= ''; + } elseif ($body) { + $html .= ''; + } + + $html .= '_line ? ' class="line" data-start="' + . ($start + $key) . '" data-end="' . ($start + $key) + . '" data-id="' . $this->_uniqid . '"' : '') . '>'; + + foreach ($columns as $subKey => $column) { + list ($num, $text) = $column; + $tag = $head ? 'th' : 'td'; + + $html .= "<{$tag}"; + if ($num > 1) { + $html .= " colspan=\"{$num}\""; + } + + if (isset($aligns[$subKey]) && $aligns[$subKey] != 'none') { + $html .= " align=\"{$aligns[$subKey]}\""; + } + + $html .= '>' . $this->parseInline($text) . ""; + } + + $html .= ''; + + if ($head) { + $html .= ''; + } elseif ($body) { + $body = false; + } + } + + if ($body !== null) { + $html .= ''; + } + + $html .= '
    '; + return $html; + } + + /** + * parseHr + * + * @param array $lines + * @param array $value + * @param int $start + * @return string + */ + private function parseHr($lines, $value, $start) + { + return $this->_line ? '
    ' : '
    '; + } + + /** + * parseNormal + * + * @param array $lines + * @param bool $inline + * @param int $start + * @return string + */ + private function parseNormal(array $lines, $inline = false, $start) + { + foreach ($lines as $key => &$line) { + $line = $this->parseInline($line); + + if (!preg_match("/^\s*$/", $line)) { + $line = $this->markLine($start + $key) . $line; + } + } + + $str = trim(implode("\n", $lines)); + $str = preg_replace("/(\n\s*){2,}/", "

    ", $str); + $str = preg_replace("/\n/", "
    ", $str); + + return preg_match("/^\s*$/", $str) ? '' : ($inline ? $str : "

    {$str}

    "); + } + + /** + * parseFootnote + * + * @param array $lines + * @param array $value + * @return string + */ + private function parseFootnote(array $lines, array $value) + { + list($space, $note) = $value; + $index = array_search($note, $this->_footnotes); + + if (false !== $index) { + $lines[0] = preg_replace("/^\[\^((?:[^\]]|\\]|\\[)+?)\]:/", '', $lines[0]); + $this->_footnotes[$index] = $lines; + } + + return ''; + } + + /** + * parseDefine + * + * @return string + */ + private function parseDefinition() + { + return ''; + } + + /** + * parseHtml + * + * @param array $lines + * @param string $type + * @param int $start + * @return string + */ + private function parseHtml(array $lines, $type, $start) + { + foreach ($lines as &$line) { + $line = $this->parseInline($line, + isset($this->_specialWhiteList[$type]) ? $this->_specialWhiteList[$type] : ''); + } + + return implode("\n", $this->markLines($lines, $start)); + } + + /** + * @param $url + * @return string + */ + public function cleanUrl($url) + { + if (preg_match("/^\s*((http|https|ftp|mailto):[\p{L}_a-z0-9-:\.\*\/%#!@\?\+=~\|\,&\(\)]+)/iu", $url, $matches)) { + return $matches[1]; + } elseif (preg_match("/^\s*([\p{L}_a-z0-9-:\.\*\/%#!@\?\+=~\|\,&]+)/iu", $url, $matches)) { + return $matches[1]; + } else { + return '#'; + } + } + + /** + * @param $str + * @return mixed + */ + public function escapeBracket($str) + { + return str_replace( + array('\[', '\]', '\(', '\)'), array('[', ']', '(', ')'), $str + ); + } + + /** + * startBlock + * + * @param mixed $type + * @param mixed $start + * @param mixed $value + * @return $this + */ + private function startBlock($type, $start, $value = null) + { + $this->_pos++; + $this->_current = $type; + + $this->_blocks[$this->_pos] = array($type, $start, $start, $value); + + return $this; + } + + /** + * endBlock + * + * @return $this + */ + private function endBlock() + { + $this->_current = 'normal'; + return $this; + } + + /** + * isBlock + * + * @param mixed $type + * @param mixed $value + * @return bool + */ + private function isBlock($type, $value = null) + { + return $this->_current == $type + && (null === $value ? true : $this->_blocks[$this->_pos][3] == $value); + } + + /** + * getBlock + * + * @return array + */ + private function getBlock() + { + return isset($this->_blocks[$this->_pos]) ? $this->_blocks[$this->_pos] : null; + } + + /** + * setBlock + * + * @param mixed $to + * @param mixed $value + * @return $this + */ + private function setBlock($to = null, $value = null) + { + if (null !== $to) { + $this->_blocks[$this->_pos][2] = $to; + } + + if (null !== $value) { + $this->_blocks[$this->_pos][3] = $value; + } + + return $this; + } + + /** + * backBlock + * + * @param mixed $step + * @param mixed $type + * @param mixed $value + * @return $this + */ + private function backBlock($step, $type, $value = null) + { + if ($this->_pos < 0) { + return $this->startBlock($type, 0, $value); + } + + $last = $this->_blocks[$this->_pos][2]; + $this->_blocks[$this->_pos][2] = $last - $step; + + if ($this->_blocks[$this->_pos][1] <= $this->_blocks[$this->_pos][2]) { + $this->_pos++; + } + + $this->_current = $type; + $this->_blocks[$this->_pos] = array( + $type, $last - $step + 1, $last, $value + ); + + return $this; + } + + /** + * @return $this + */ + private function combineBlock() + { + if ($this->_pos < 1) { + return $this; + } + + $prev = $this->_blocks[$this->_pos - 1]; + $current = $this->_blocks[$this->_pos]; + + $prev[2] = $current[2]; + $this->_blocks[$this->_pos - 1] = $prev; + $this->_current = $prev[0]; + unset($this->_blocks[$this->_pos]); + $this->_pos--; + + return $this; + } + +} \ No newline at end of file diff --git a/src/Publisher/Config.php b/src/Publisher/Config.php new file mode 100644 index 0000000..80b2a80 --- /dev/null +++ b/src/Publisher/Config.php @@ -0,0 +1,121 @@ + [ // key必须与元素内字段key保持一致 +// 'name' => '性别',// 任意名称,仅作展示用途 +// 'key' => 'sex',// 配置项对应key +// 'type' => PUBLISHER_CONFIG_TYPE_INT | PUBLISHER_CONFIG_VALUE_MULTI,// 配置项输入类型字符 +// 'options' => [// 所有可选项,PUBLISHER_CONFIG_VALUE_SINGLE则忽略该字段 +// [ +// 'name' => '男', +// 'value' => 1, +// ], +// [ +// 'name' => '女', +// 'value' => 2, +// ], +// ], +// ], + ]; + + /** + * @var array $_configs key-value + */ + protected $_configs = [ +// 'sex' => 1, + ]; + + /** + * @param array $configDefine + * @throws MultiArticleException + */ + public function setConfigDefine(array $configDefine) + { + $this->_configDefine = $configDefine; + $this->validateConfigDefine(); + } + + public function setConfig(string $key, $value) + { + $this->_configs[$key] = $value; + } + + public function setConfigs(array $configs) + { + foreach ($configs as $key => $value) { + $this->_configs[$key] = $value; + } + } + + /** + * 获取配置定义 + * + * @return array + */ + public function getConfigDefine() + { + return $this->_configDefine; + } + + /** + * @param string $key + * @return array|null + */ + public function getConfig(string $key) + { + return $this->_configs[$key] ?? null; + } + + public function getConfigs() + { + return $this->_configs; + } + + /** + * 格式判断,保障配置项格式合法 + * + * @throws MultiArticleException + */ + protected function validateConfigDefine() + { + foreach ($this->_configDefine as $key => $value) { + + // 格式判断 + $flag = $value['name'] ?? false && + $value['key'] ?? false && + $value['key'] === $key && + is_string($key) && + (!($value['type'] & PUBLISHER_CONFIG_VALUE_MULTI) || $value['options'] ?? false); + if (!$flag) { + throw new MultiArticleException('Define format is error, see EFrame\MultiArticle\Publisher\Config'); + } + } + } +} \ No newline at end of file diff --git a/src/Publisher/Lx/LxPublisher.php b/src/Publisher/Lx/LxPublisher.php new file mode 100644 index 0000000..dbe8c90 --- /dev/null +++ b/src/Publisher/Lx/LxPublisher.php @@ -0,0 +1,140 @@ +initConfigDefine(); + $this->initStaffIdOption(); + } + + public function publish() + { + $attributes = [ + 'title' => $this->_article->getTitle(), + 'content' => $this->_article->toType(ARTICLE_CONTENT_TYPE_MD)->getContent(), + 'is_markdown' => 1, + ]; + $options = [ + 'privilege_type' => $this->getConfig('privilegeType'), + 'source' => $this->getConfig('source'), + 'reship_url' => $this->getConfig('reshipUrl'), + ]; + $response = $this->postDoc($this->getConfig('staffId'), $attributes, $options); + echo \GuzzleHttp\json_encode($response);exit; + } + +// /** +// * 自行在子类中实现getToken,便于实现缓存方案 +// * +// * @return string +// */ +// abstract public function getAccessToken(); + + /** + * 初始化人员列表 + */ + protected function initStaffIdOption() + { + $page = 1; + $lastPage = 1; + $data = [ + 'per_page' => 100, + ]; + while ($page <= $lastPage) { + // 获取数据 + $data['page'] = $page; + $response = $this->get('staffs', $data); + + // 更新信息 + $staffs = $response['data']; + $lastPage = $response['meta']['last_page']; + + // 添加用户列表 + foreach ($staffs as $staff) { + $this->_configDefine['staffId']['options'][] = [ + 'name' => $staff['attributes']['english_name'] ?? $staff['attributes']['name'], + 'value' => $staff['id'], + ]; + } + + $page = $response['meta']['current_page'] + 1; + } + } + + /** + * @throws \EFrame\MultiArticle\Exception\MultiArticleException + */ + protected function initConfigDefine() + { + $this->setConfigDefine([ + 'staffId' => [ // key必须与元素内字段key保持一致 + 'name' => '乐享id',// 任意名称,仅作展示用途 + 'key' => 'staffId',// 配置项对应key + 'type' => PUBLISHER_CONFIG_TYPE_TXT | PUBLISHER_CONFIG_VALUE_SINGLE,// 配置项输入类型字符 + 'options' => [], + ], + 'privilegeType' => [ + 'name' => '公开权限', + 'key' => 'privilegeType',// 配置项对应key + 'type' => PUBLISHER_CONFIG_TYPE_INT | PUBLISHER_CONFIG_VALUE_SINGLE,// 配置项输入类型字符 + 'options' => [ + [ + 'name' => '公开', + 'value' => 0, + ], + [ + 'name' => '部分人可见', + 'value' => 1, + ], + [ + 'name' => '仅创建者可见', + 'value' => 2, + ], + ], + ], + 'source' => [ + 'name' => '来源', + 'key' => 'privilegeType',// 配置项对应key + 'type' => PUBLISHER_CONFIG_TYPE_INT | PUBLISHER_CONFIG_VALUE_SINGLE,// 配置项输入类型字符 + 'options' => [ + [ + 'name' => '原创', + 'value' => 'original', + ], + [ + 'name' => '转载', + 'value' => 'reship', + ], + ], + ], + 'reshipUrl' => [ + 'name' => '来源', + 'key' => 'reshipUrl',// 配置项对应key + 'type' => PUBLISHER_CONFIG_TYPE_INT | PUBLISHER_CONFIG_VALUE_SINGLE,// 配置项输入类型字符 + ], + ]); + } +} \ No newline at end of file diff --git a/src/Publisher/Publisher.php b/src/Publisher/Publisher.php new file mode 100644 index 0000000..9f993e9 --- /dev/null +++ b/src/Publisher/Publisher.php @@ -0,0 +1,44 @@ +_article = $article; + } + + // 配置信息 + if ($configs) { + $this->setConfigs($configs->getConfigs()); + } + } + + abstract public function publish(); + + public function setArticle(ArticleInterface $article) + { + $this->_article = $article; + } +} \ No newline at end of file