├── install.php
├── uninstall.php
├── update.php
├── CHANGELOG.md
├── assets
└── css
│ └── backend.css
├── pages
├── index.php
├── help.readme.php
├── help.license.php
├── help.changelog.php
└── docs.php
├── lang
└── de_de.lang
├── boot.php
├── package.yml
├── README.md
├── LICENSE.md
├── lib
├── rex_api_docs.php
└── rex_api_docs_parsedown.php
└── links.php
/install.php:
--------------------------------------------------------------------------------
1 | i18n('title'));
7 |
8 | $subpage = rex_be_controller::getCurrentPagePart(2);
9 |
10 | include rex_be_controller::getCurrentPageObject()->getSubPath();
11 |
12 |
13 |
--------------------------------------------------------------------------------
/pages/help.readme.php:
--------------------------------------------------------------------------------
1 | getPath('README.md'));
3 |
4 | $parsedown = new rex_api_docs_parsedown();
5 | $content = $parsedown->text($file);
6 |
7 | $fragment = new rex_fragment();
8 | $fragment->setVar('title', $this->i18n('help_readme'));
9 | $fragment->setVar('body', $content, false);
10 |
11 | echo $fragment->parse('core/page/section.php');
12 |
--------------------------------------------------------------------------------
/pages/help.license.php:
--------------------------------------------------------------------------------
1 | getPath('LICENSE.md'));
3 |
4 | $parsedown = new rex_api_docs_parsedown();
5 | $content = $parsedown->text($file);
6 |
7 | $fragment = new rex_fragment();
8 | $fragment->setVar('title', $this->i18n('help_license'));
9 | $fragment->setVar('body', $content, false);
10 |
11 | echo $fragment->parse('core/page/section.php');
12 |
--------------------------------------------------------------------------------
/pages/help.changelog.php:
--------------------------------------------------------------------------------
1 | getPath('CHANGELOG.md'));
3 |
4 | $parsedown = new rex_api_docs_parsedown();
5 | $content = $parsedown->text($file);
6 |
7 | $fragment = new rex_fragment();
8 | $fragment->setVar('title', $this->i18n('help_changelog'));
9 | $fragment->setVar('body', $content, false);
10 |
11 | echo $fragment->parse('core/page/section.php');
12 |
--------------------------------------------------------------------------------
/lang/de_de.lang:
--------------------------------------------------------------------------------
1 | api_docs_title = API Docs
2 |
3 | api_docs_docs = Docs
4 | api_docs_api = API
5 | api_docs_r5_changes = R5 Änderungen
6 | api_docs_extension_points = Extension Points
7 | api_docs_rexvars = REDAXO Variablen
8 | api_docs_cheatsheet = R4 Cheatsheet
9 | api_docs_code_snippets = Code Snippets
10 | api_docs_howtos = How To's
11 | api_docs_faq = FAQ
12 | api_docs_github = GitHub
13 | api_docs_handbook = Handbuch
14 |
15 | api_docs_help = Hilfe
16 | api_docs_help_readme = Readme
17 | api_docs_help_changelog = Changelog
18 | api_docs_help_license = Lizenz
19 |
--------------------------------------------------------------------------------
/boot.php:
--------------------------------------------------------------------------------
1 | getAssetsUrl('css/backend.css'));
12 |
13 | rex_extension::register('PAGE_BODY_ATTR', function (rex_extension_point $ep) {
14 | $subject = $ep->getSubject();
15 | $subject['class'][] = 'api-docs-addon';
16 |
17 | $ep->setSubject($subject);
18 | });
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/package.yml:
--------------------------------------------------------------------------------
1 | package: api_docs
2 | version: '1.2.0-dev'
3 | author: Friends Of REDAXO
4 | supportpage: https://github.com/FriendsOfREDAXO/api_docs
5 |
6 | page:
7 | title: translate:api_docs_title
8 | perm: admin[]
9 | pjax: false
10 | icon: rex-icon fa-book
11 | subpages:
12 | docs: { title: 'translate:docs' }
13 | help:
14 | title: 'translate:help'
15 | perm: admin[]
16 | subpages:
17 | readme: { title: 'translate:help_readme' }
18 | changelog: { title: 'translate:help_changelog' }
19 | license: { title: 'translate:help_license' }
20 |
21 | requires:
22 | redaxo: '^5.0'
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | API Docs AddOn für REDAXO 5
2 | ===========================
3 |
4 | Umfangreiche Linksammlung zu REDAXO 5 Entwickler Resourcen
5 |
6 | Hinzufügen eigener Links
7 | ------------------------
8 |
9 | In der `boot.php` des eigenen AddOns den `API_DOCS` Extension Point registrieren.
10 |
11 | ```php
12 | if (rex::isBackend()) {
13 | rex_extension::register('API_DOCS', function(rex_extension_point $ep) {
14 | $subject = $ep->getSubject();
15 |
16 | if (isset($subject['api']['links'])) {
17 | $subject['api']['links'][] = [
18 | 'title' => rex_i18n::msg('xcore_api_docs_title'),
19 | 'description' => rex_i18n::msg('xcore_api_docs_description'),
20 | 'href' => rex_url::backendPage('xcore/rexx_api'),
21 | 'open_in_new_window' => false
22 | ];
23 | }
24 |
25 | $ep->setSubject($subject);
26 | });
27 | }
28 | ```
29 |
30 | Hinweise
31 | --------
32 |
33 | * Getestet mit REDAXO 5.3
34 | * AddOn-Ordner lautet: `api_docs`
35 |
36 | Changelog
37 | ---------
38 |
39 | siehe `CHANGELOG.md` des AddOns
40 |
41 | Lizenz
42 | ------
43 |
44 | MIT-Lizenz, siehe `LICENSE.md` des AddOns
45 |
46 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Friends Of REDAXO
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/rex_api_docs.php:
--------------------------------------------------------------------------------
1 | ';
10 | }
11 |
12 | $targetAttr = 'target="_blank"';
13 | $icon = '';
14 |
15 | if (isset($link['open_in_new_window'])) {
16 | if ($link['open_in_new_window']) {
17 | $targetAttr = 'target="_blank"';
18 | $icon = '';
19 | } else {
20 | $targetAttr = '';
21 | $icon = '';
22 | }
23 | }
24 |
25 | if (strpos($link['href'], 'http') === false) {
26 | $shownLink = $_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['HTTP_HOST'] . dirname($_SERVER['PHP_SELF']) . '/' . $link['href'];
27 | } else {
28 | $shownLink = $link['href'];
29 | }
30 |
31 | $content .= '
32 |
' . $link['title'] . '
33 |
34 | ' . $description . '
35 | ' . $shownLink . ' ' . $icon . '
36 |
37 | ';
38 | }
39 |
40 | return $content;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/pages/docs.php:
--------------------------------------------------------------------------------
1 | ';
5 | $content .= '';
6 |
7 | $first = true;
8 |
9 | foreach ($apiDocs as $section => $value) {
10 | if ($first) {
11 | $class = 'active';
12 | $first = false;
13 | } else {
14 | $class = '';
15 | }
16 |
17 | $content .= '- ' . rex_i18n::msg('api_docs_' . $section) . '
';
18 | }
19 |
20 | $content .= '
';
21 | $content .= '';
22 |
23 | $first = true;
24 |
25 | foreach ($apiDocs as $section => $value) {
26 | if ($first) {
27 | $class = 'active';
28 | $first = false;
29 | } else {
30 | $class = '';
31 | }
32 |
33 | $content .= '
' . rex_api_docs::getLinks($apiDocs[$section]) . '
';
34 | }
35 |
36 | $content .= '
';
37 | $content .= '';
38 |
39 | $fragment = new rex_fragment();
40 | $fragment->setVar('title', rex_i18n::msg('api_docs_docs'), false);
41 | $fragment->setVar('body', $content, false);
42 |
43 | echo $fragment->parse('core/page/section.php');
44 |
--------------------------------------------------------------------------------
/links.php:
--------------------------------------------------------------------------------
1 | [
5 | 'title' => rex_i18n::msg('api_docs_api'),
6 | 'links' => [
7 | [
8 | 'title' => 'REDAXO 5 API',
9 | 'description' => 'Die komplette R5 API',
10 | 'href' => 'http://www.redaxo.org/docs/master/'
11 | ],
12 | ]
13 | ],
14 | 'handbook' => [
15 | 'title' => rex_i18n::msg('api_docs_handbook'),
16 | 'links' => [
17 | [
18 | 'title' => 'REDAXO 5 Handbuch',
19 | 'description' => 'Das REDAXO 5 Handbuch',
20 | 'href' => 'https://redaxo.org/doku/master'
21 | ],
22 | ]
23 | ],
24 | 'extension_points' => [
25 | 'title' => rex_i18n::msg('api_docs_extension_points'),
26 | 'links' => [
27 | [
28 | 'title' => 'Extension Points aus Entwickler-Handbuch',
29 | 'description' => 'Extension Points sind unten auf der Seite aufgeführt',
30 | 'href' => 'https://redaxo.org/doku/master/extension-points'
31 | ],
32 | [
33 | 'title' => 'Cheatsheet AddOn',
34 | 'description' => 'Extrahiert und listet on-the-fly alle Extension Points aus Core und AddOns',
35 | 'href' => 'https://github.com/tbaddade/redaxo_cheatsheet'
36 | ],
37 | [
38 | 'title' => 'Änderungen in REDAXO 5',
39 | 'description' => 'Siehe Abschnitt Extension Points',
40 | 'href' => 'https://redaxo.org/doku/master/aenderungen-v4-v5'
41 | ],
42 | ]
43 | ],
44 | 'rexvars' => [
45 | 'title' => rex_i18n::msg('api_docs_rexvars'),
46 | 'links' => [
47 | [
48 | 'title' => 'REDAXO Variablen aus Dokumentation',
49 | 'description' => 'Enthält zahlreiche Beispiele',
50 | 'href' => 'https://redaxo.org/doku/master/redaxo-variablen'
51 | ],
52 | [
53 | 'title' => 'Cheatsheet AddOn',
54 | 'description' => 'Extrahiert und listet on-the-fly alle REDAXO Variablen',
55 | 'href' => 'https://github.com/tbaddade/redaxo_cheatsheet'
56 | ],
57 | [
58 | 'title' => 'Änderungen in REDAXO 5',
59 | 'description' => 'Siehe Abschnitt REX_VAR und $REX weiter oben',
60 | 'href' => 'https://redaxo.org/doku/master/aenderungen-v4-v5'
61 | ],
62 | ]
63 | ],
64 | 'r5_changes' => [
65 | 'title' => rex_i18n::msg('api_docs_r5_changes'),
66 | 'links' => [
67 | [
68 | 'title' => 'Änderungen in REDAXO 5',
69 | 'description' => 'Alle Änderungen von R4 zu R5 im Überblick',
70 | 'href' => 'https://redaxo.org/doku/master/aenderungen-v4-v5'
71 | ],
72 | ]
73 | ],
74 | 'code_snippets' => [
75 | 'title' => rex_i18n::msg('api_docs_code_snippets'),
76 | 'links' => [
77 | [
78 | 'title' => 'Tips und Tricks',
79 | 'description' => 'Code Snippet Sammlung von Friends Of REDAXO',
80 | 'href' => 'https://github.com/FriendsOfREDAXO/tricks'
81 | ],
82 | [
83 | 'title' => 'Weitere Tips und Tricks',
84 | 'description' => 'Offene Issues aus der Code Snippet Sammlung von Friends Of REDAXO',
85 | 'href' => 'https://github.com/FriendsOfREDAXO/tricks/issues'
86 | ],
87 | [
88 | 'title' => 'Passwort vergessen',
89 | 'description' => 'Thema Passwort vergessen aus der neuen Dokumentation',
90 | 'href' => 'https://github.com/redaxo/docs/blob/master/passwort-vergessen.md'
91 | ],
92 | [
93 | 'title' => 'Aktionen',
94 | 'description' => 'Thema Aktionen aus der neuen Dokumentation',
95 | 'href' => 'https://redaxo.org/doku/master/modul-aktionen'
96 | ],
97 | [
98 | 'title' => 'Fragmente',
99 | 'description' => 'Thema Fragmente aus der neuen Dokumentation',
100 | 'href' => 'https://redaxo.org/doku/master/fragmente'
101 | ],
102 | [
103 | 'title' => 'Paginierung',
104 | 'description' => 'Thema Paginierung aus der neuen Dokumentation',
105 | 'href' => 'https://redaxo.org/doku/master/paginierung'
106 | ],
107 | ]
108 | ],
109 | 'faq' => [
110 | 'title' => rex_i18n::msg('api_docs_faq'),
111 | 'links' => [
112 | [
113 | 'title' => 'FAQ by Alex Plus',
114 | 'description' => 'Aktuell nur als Issues auf GitHub',
115 | 'href' => 'https://github.com/redaxo/docs/issues?utf8=%E2%9C%93&q=Q%26A%20is%3Aissue%20author%3Aalexplusde'
116 | ],
117 | ]
118 | ],
119 | 'github' => [
120 | 'title' => rex_i18n::msg('api_docs_github'),
121 | 'links' => [
122 | [
123 | 'title' => 'REDAXO Core',
124 | 'description' => 'GitHub Repository des Cores',
125 | 'href' => 'https://github.com/redaxo/redaxo/'
126 | ],
127 | [
128 | 'title' => 'AddOns von REDAXO',
129 | 'description' => 'GitHub Repository mit AddOns wie Media Manager, Meta Info und Backup',
130 | 'href' => 'https://github.com/redaxo/redaxo/tree/master/redaxo/src/addons'
131 | ],
132 | [
133 | 'title' => 'AddOns von Yakamara',
134 | 'description' => 'GitHub Repository mit AddOns wie yForm, yCom und yRewrite',
135 | 'href' => ' https://github.com/yakamara/'
136 | ],
137 | [
138 | 'title' => 'AddOns von Friends Of REDAXO',
139 | 'description' => 'GitHub Repository mit AddOns wie Developer, MForm, Search It',
140 | 'href' => 'https://github.com/FriendsOfREDAXO'
141 | ],
142 | [
143 | 'title' => 'AddOns von tbaddade',
144 | 'description' => 'GitHub Repository mit AddOns Url, Sprog und Cheatsheet',
145 | 'href' => 'https://github.com/tbaddade/'
146 | ],
147 | [
148 | 'title' => 'AddOns von RexDude',
149 | 'description' => 'GitHub Repository mit AddOns wie CKEditor, Globale Einstellungen und Navigation Factory',
150 | 'href' => 'https://github.com/RexDude/'
151 | ],
152 | ]
153 | ],
154 | 'cheatsheet' => [
155 | 'title' => rex_i18n::msg('api_docs_cheatsheet'),
156 | 'links' => [
157 | [
158 | 'title' => 'Altes Cheatsheet für REDAXO 4 von tbaddade',
159 | 'description' => 'Siehe REDAXO API und Änderungen in REDAXO 5 für Umschreibungen',
160 | 'href' => 'http://blumbeet.com/files/cheatsheet_redaxo_42.pdf'
161 | ],
162 | [
163 | 'title' => 'Altes Cheatsheet für REDAXO 4 von jdlx',
164 | 'description' => 'Siehe REDAXO API und Änderungen in REDAXO 5 für Umschreibungen',
165 | 'href' => 'http://rexdev.de/cheatsheets/rex450.html'
166 | ],
167 | ]
168 | ],
169 | ];
170 |
--------------------------------------------------------------------------------
/lib/rex_api_docs_parsedown.php:
--------------------------------------------------------------------------------
1 | breaksEnabled = $breaksEnabled;
6 | }
7 |
8 | protected function blockFencedCodeComplete($Block) {
9 | $text = $Block['element']['text']['text'];
10 |
11 | $text = self::highlight($text, $Block['element']['text']['attributes']['class']);
12 |
13 | $Block['element']['text']['text'] = $text;
14 |
15 | return $Block;
16 | }
17 |
18 | // credits: http://php.net/manual/de/function.highlight-string.php#118550
19 | public static function highlight($text, $fileExt = '') {
20 | if (strpos($fileExt, 'lanuage-') === 0) {
21 | $fileExt = substr($fileExt, strlen('lanuage-'));
22 | }
23 |
24 | if ($fileExt == "php") {
25 | ini_set("highlight.comment", "#008000");
26 | ini_set("highlight.default", "#000000");
27 | ini_set("highlight.html", "#808080");
28 | ini_set("highlight.keyword", "#0000BB; font-weight: bold");
29 | ini_set("highlight.string", "#DD0000");
30 | } else if ($fileExt == "html" || $fileExt == "javascript") {
31 | ini_set("highlight.comment", "green");
32 | ini_set("highlight.default", "#CC0000");
33 | ini_set("highlight.html", "#000000");
34 | ini_set("highlight.keyword", "black; font-weight: bold");
35 | ini_set("highlight.string", "#0000FF");
36 | }
37 |
38 | $text = trim($text);
39 | $text = highlight_string("\\|", "", $text, 1); // remove prefix
42 | $text = preg_replace("|\\\$|", "", $text, 1); // remove suffix 1
43 | $text = trim($text); // remove line breaks
44 | $text = preg_replace("|\\\$|", "", $text, 1); // remove suffix 2
45 | $text = trim($text); // remove line breaks
46 | $text = preg_replace("|^(\\)(<\\?php )(.*?)(\\)|", "\$1\$3\$4", $text); // remove custom added "DefinitionData = array();
64 |
65 | # standardize line breaks
66 | $text = str_replace(array("\r\n", "\r"), "\n", $text);
67 |
68 | # remove surrounding line breaks
69 | $text = trim($text, "\n");
70 |
71 | # split text into lines
72 | $lines = explode("\n", $text);
73 |
74 | # iterate through lines to identify blocks
75 | $markup = $this->lines($lines);
76 |
77 | # trim line breaks
78 | $markup = trim($markup, "\n");
79 |
80 | return $markup;
81 | }
82 |
83 | #
84 | # Setters
85 | #
86 |
87 | function setBreaksEnabled($breaksEnabled)
88 | {
89 | $this->breaksEnabled = $breaksEnabled;
90 |
91 | return $this;
92 | }
93 |
94 | protected $breaksEnabled;
95 |
96 | function setMarkupEscaped($markupEscaped)
97 | {
98 | $this->markupEscaped = $markupEscaped;
99 |
100 | return $this;
101 | }
102 |
103 | protected $markupEscaped;
104 |
105 | function setUrlsLinked($urlsLinked)
106 | {
107 | $this->urlsLinked = $urlsLinked;
108 |
109 | return $this;
110 | }
111 |
112 | protected $urlsLinked = true;
113 |
114 | #
115 | # Lines
116 | #
117 |
118 | protected $BlockTypes = array(
119 | '#' => array('Header'),
120 | '*' => array('Rule', 'List'),
121 | '+' => array('List'),
122 | '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
123 | '0' => array('List'),
124 | '1' => array('List'),
125 | '2' => array('List'),
126 | '3' => array('List'),
127 | '4' => array('List'),
128 | '5' => array('List'),
129 | '6' => array('List'),
130 | '7' => array('List'),
131 | '8' => array('List'),
132 | '9' => array('List'),
133 | ':' => array('Table'),
134 | '<' => array('Comment', 'Markup'),
135 | '=' => array('SetextHeader'),
136 | '>' => array('Quote'),
137 | '[' => array('Reference'),
138 | '_' => array('Rule'),
139 | '`' => array('FencedCode'),
140 | '|' => array('Table'),
141 | '~' => array('FencedCode'),
142 | );
143 |
144 | # ~
145 |
146 | protected $unmarkedBlockTypes = array(
147 | 'Code',
148 | );
149 |
150 | #
151 | # Blocks
152 | #
153 |
154 | protected function lines(array $lines)
155 | {
156 | $CurrentBlock = null;
157 |
158 | foreach ($lines as $line)
159 | {
160 | if (chop($line) === '')
161 | {
162 | if (isset($CurrentBlock))
163 | {
164 | $CurrentBlock['interrupted'] = true;
165 | }
166 |
167 | continue;
168 | }
169 |
170 | if (strpos($line, "\t") !== false)
171 | {
172 | $parts = explode("\t", $line);
173 |
174 | $line = $parts[0];
175 |
176 | unset($parts[0]);
177 |
178 | foreach ($parts as $part)
179 | {
180 | $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
181 |
182 | $line .= str_repeat(' ', $shortage);
183 | $line .= $part;
184 | }
185 | }
186 |
187 | $indent = 0;
188 |
189 | while (isset($line[$indent]) and $line[$indent] === ' ')
190 | {
191 | $indent ++;
192 | }
193 |
194 | $text = $indent > 0 ? substr($line, $indent) : $line;
195 |
196 | # ~
197 |
198 | $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
199 |
200 | # ~
201 |
202 | if (isset($CurrentBlock['continuable']))
203 | {
204 | $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
205 |
206 | if (isset($Block))
207 | {
208 | $CurrentBlock = $Block;
209 |
210 | continue;
211 | }
212 | else
213 | {
214 | if ($this->isBlockCompletable($CurrentBlock['type']))
215 | {
216 | $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
217 | }
218 | }
219 | }
220 |
221 | # ~
222 |
223 | $marker = $text[0];
224 |
225 | # ~
226 |
227 | $blockTypes = $this->unmarkedBlockTypes;
228 |
229 | if (isset($this->BlockTypes[$marker]))
230 | {
231 | foreach ($this->BlockTypes[$marker] as $blockType)
232 | {
233 | $blockTypes []= $blockType;
234 | }
235 | }
236 |
237 | #
238 | # ~
239 |
240 | foreach ($blockTypes as $blockType)
241 | {
242 | $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
243 |
244 | if (isset($Block))
245 | {
246 | $Block['type'] = $blockType;
247 |
248 | if ( ! isset($Block['identified']))
249 | {
250 | $Blocks []= $CurrentBlock;
251 |
252 | $Block['identified'] = true;
253 | }
254 |
255 | if ($this->isBlockContinuable($blockType))
256 | {
257 | $Block['continuable'] = true;
258 | }
259 |
260 | $CurrentBlock = $Block;
261 |
262 | continue 2;
263 | }
264 | }
265 |
266 | # ~
267 |
268 | if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
269 | {
270 | $CurrentBlock['element']['text'] .= "\n".$text;
271 | }
272 | else
273 | {
274 | $Blocks []= $CurrentBlock;
275 |
276 | $CurrentBlock = $this->paragraph($Line);
277 |
278 | $CurrentBlock['identified'] = true;
279 | }
280 | }
281 |
282 | # ~
283 |
284 | if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
285 | {
286 | $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
287 | }
288 |
289 | # ~
290 |
291 | $Blocks []= $CurrentBlock;
292 |
293 | unset($Blocks[0]);
294 |
295 | # ~
296 |
297 | $markup = '';
298 |
299 | foreach ($Blocks as $Block)
300 | {
301 | if (isset($Block['hidden']))
302 | {
303 | continue;
304 | }
305 |
306 | $markup .= "\n";
307 | $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
308 | }
309 |
310 | $markup .= "\n";
311 |
312 | # ~
313 |
314 | return $markup;
315 | }
316 |
317 | protected function isBlockContinuable($Type)
318 | {
319 | return method_exists($this, 'block'.$Type.'Continue');
320 | }
321 |
322 | protected function isBlockCompletable($Type)
323 | {
324 | return method_exists($this, 'block'.$Type.'Complete');
325 | }
326 |
327 | #
328 | # Code
329 |
330 | protected function blockCode($Line, $Block = null)
331 | {
332 | if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
333 | {
334 | return;
335 | }
336 |
337 | if ($Line['indent'] >= 4)
338 | {
339 | $text = substr($Line['body'], 4);
340 |
341 | $Block = array(
342 | 'element' => array(
343 | 'name' => 'pre',
344 | 'handler' => 'element',
345 | 'text' => array(
346 | 'name' => 'code',
347 | 'text' => $text,
348 | ),
349 | ),
350 | );
351 |
352 | return $Block;
353 | }
354 | }
355 |
356 | protected function blockCodeContinue($Line, $Block)
357 | {
358 | if ($Line['indent'] >= 4)
359 | {
360 | if (isset($Block['interrupted']))
361 | {
362 | $Block['element']['text']['text'] .= "\n";
363 |
364 | unset($Block['interrupted']);
365 | }
366 |
367 | $Block['element']['text']['text'] .= "\n";
368 |
369 | $text = substr($Line['body'], 4);
370 |
371 | $Block['element']['text']['text'] .= $text;
372 |
373 | return $Block;
374 | }
375 | }
376 |
377 | protected function blockCodeComplete($Block)
378 | {
379 | $text = $Block['element']['text']['text'];
380 |
381 | $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
382 |
383 | $Block['element']['text']['text'] = $text;
384 |
385 | return $Block;
386 | }
387 |
388 | #
389 | # Comment
390 |
391 | protected function blockComment($Line)
392 | {
393 | if ($this->markupEscaped)
394 | {
395 | return;
396 | }
397 |
398 | if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
399 | {
400 | $Block = array(
401 | 'markup' => $Line['body'],
402 | );
403 |
404 | if (preg_match('/-->$/', $Line['text']))
405 | {
406 | $Block['closed'] = true;
407 | }
408 |
409 | return $Block;
410 | }
411 | }
412 |
413 | protected function blockCommentContinue($Line, array $Block)
414 | {
415 | if (isset($Block['closed']))
416 | {
417 | return;
418 | }
419 |
420 | $Block['markup'] .= "\n" . $Line['body'];
421 |
422 | if (preg_match('/-->$/', $Line['text']))
423 | {
424 | $Block['closed'] = true;
425 | }
426 |
427 | return $Block;
428 | }
429 |
430 | #
431 | # Fenced Code
432 |
433 | protected function blockFencedCode($Line)
434 | {
435 | if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
436 | {
437 | $Element = array(
438 | 'name' => 'code',
439 | 'text' => '',
440 | );
441 |
442 | if (isset($matches[1]))
443 | {
444 | $class = 'language-'.$matches[1];
445 |
446 | $Element['attributes'] = array(
447 | 'class' => $class,
448 | );
449 | }
450 |
451 | $Block = array(
452 | 'char' => $Line['text'][0],
453 | 'element' => array(
454 | 'name' => 'pre',
455 | 'handler' => 'element',
456 | 'text' => $Element,
457 | ),
458 | );
459 |
460 | return $Block;
461 | }
462 | }
463 |
464 | protected function blockFencedCodeContinue($Line, $Block)
465 | {
466 | if (isset($Block['complete']))
467 | {
468 | return;
469 | }
470 |
471 | if (isset($Block['interrupted']))
472 | {
473 | $Block['element']['text']['text'] .= "\n";
474 |
475 | unset($Block['interrupted']);
476 | }
477 |
478 | if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
479 | {
480 | $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
481 |
482 | $Block['complete'] = true;
483 |
484 | return $Block;
485 | }
486 |
487 | $Block['element']['text']['text'] .= "\n".$Line['body'];
488 |
489 | return $Block;
490 | }
491 |
492 | #
493 | # Header
494 |
495 | protected function blockHeader($Line)
496 | {
497 | if (isset($Line['text'][1]))
498 | {
499 | $level = 1;
500 |
501 | while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
502 | {
503 | $level ++;
504 | }
505 |
506 | if ($level > 6)
507 | {
508 | return;
509 | }
510 |
511 | $text = trim($Line['text'], '# ');
512 |
513 | $Block = array(
514 | 'element' => array(
515 | 'name' => 'h' . min(6, $level),
516 | 'text' => $text,
517 | 'handler' => 'line',
518 | ),
519 | );
520 |
521 | return $Block;
522 | }
523 | }
524 |
525 | #
526 | # List
527 |
528 | protected function blockList($Line)
529 | {
530 | list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
531 |
532 | if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
533 | {
534 | $Block = array(
535 | 'indent' => $Line['indent'],
536 | 'pattern' => $pattern,
537 | 'element' => array(
538 | 'name' => $name,
539 | 'handler' => 'elements',
540 | ),
541 | );
542 |
543 | if($name === 'ol')
544 | {
545 | $listStart = stristr($matches[0], '.', true);
546 |
547 | if($listStart !== '1')
548 | {
549 | $Block['element']['attributes'] = array('start' => $listStart);
550 | }
551 | }
552 |
553 | $Block['li'] = array(
554 | 'name' => 'li',
555 | 'handler' => 'li',
556 | 'text' => array(
557 | $matches[2],
558 | ),
559 | );
560 |
561 | $Block['element']['text'] []= & $Block['li'];
562 |
563 | return $Block;
564 | }
565 | }
566 |
567 | protected function blockListContinue($Line, array $Block)
568 | {
569 | if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
570 | {
571 | if (isset($Block['interrupted']))
572 | {
573 | $Block['li']['text'] []= '';
574 |
575 | unset($Block['interrupted']);
576 | }
577 |
578 | unset($Block['li']);
579 |
580 | $text = isset($matches[1]) ? $matches[1] : '';
581 |
582 | $Block['li'] = array(
583 | 'name' => 'li',
584 | 'handler' => 'li',
585 | 'text' => array(
586 | $text,
587 | ),
588 | );
589 |
590 | $Block['element']['text'] []= & $Block['li'];
591 |
592 | return $Block;
593 | }
594 |
595 | if ($Line['text'][0] === '[' and $this->blockReference($Line))
596 | {
597 | return $Block;
598 | }
599 |
600 | if ( ! isset($Block['interrupted']))
601 | {
602 | $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
603 |
604 | $Block['li']['text'] []= $text;
605 |
606 | return $Block;
607 | }
608 |
609 | if ($Line['indent'] > 0)
610 | {
611 | $Block['li']['text'] []= '';
612 |
613 | $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
614 |
615 | $Block['li']['text'] []= $text;
616 |
617 | unset($Block['interrupted']);
618 |
619 | return $Block;
620 | }
621 | }
622 |
623 | #
624 | # Quote
625 |
626 | protected function blockQuote($Line)
627 | {
628 | if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
629 | {
630 | $Block = array(
631 | 'element' => array(
632 | 'name' => 'blockquote',
633 | 'handler' => 'lines',
634 | 'text' => (array) $matches[1],
635 | ),
636 | );
637 |
638 | return $Block;
639 | }
640 | }
641 |
642 | protected function blockQuoteContinue($Line, array $Block)
643 | {
644 | if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
645 | {
646 | if (isset($Block['interrupted']))
647 | {
648 | $Block['element']['text'] []= '';
649 |
650 | unset($Block['interrupted']);
651 | }
652 |
653 | $Block['element']['text'] []= $matches[1];
654 |
655 | return $Block;
656 | }
657 |
658 | if ( ! isset($Block['interrupted']))
659 | {
660 | $Block['element']['text'] []= $Line['text'];
661 |
662 | return $Block;
663 | }
664 | }
665 |
666 | #
667 | # Rule
668 |
669 | protected function blockRule($Line)
670 | {
671 | if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
672 | {
673 | $Block = array(
674 | 'element' => array(
675 | 'name' => 'hr'
676 | ),
677 | );
678 |
679 | return $Block;
680 | }
681 | }
682 |
683 | #
684 | # Setext
685 |
686 | protected function blockSetextHeader($Line, array $Block = null)
687 | {
688 | if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
689 | {
690 | return;
691 | }
692 |
693 | if (chop($Line['text'], $Line['text'][0]) === '')
694 | {
695 | $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
696 |
697 | return $Block;
698 | }
699 | }
700 |
701 | #
702 | # Markup
703 |
704 | protected function blockMarkup($Line)
705 | {
706 | if ($this->markupEscaped)
707 | {
708 | return;
709 | }
710 |
711 | if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
712 | {
713 | $element = strtolower($matches[1]);
714 |
715 | if (in_array($element, $this->textLevelElements))
716 | {
717 | return;
718 | }
719 |
720 | $Block = array(
721 | 'name' => $matches[1],
722 | 'depth' => 0,
723 | 'markup' => $Line['text'],
724 | );
725 |
726 | $length = strlen($matches[0]);
727 |
728 | $remainder = substr($Line['text'], $length);
729 |
730 | if (trim($remainder) === '')
731 | {
732 | if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
733 | {
734 | $Block['closed'] = true;
735 |
736 | $Block['void'] = true;
737 | }
738 | }
739 | else
740 | {
741 | if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
742 | {
743 | return;
744 | }
745 |
746 | if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
747 | {
748 | $Block['closed'] = true;
749 | }
750 | }
751 |
752 | return $Block;
753 | }
754 | }
755 |
756 | protected function blockMarkupContinue($Line, array $Block)
757 | {
758 | if (isset($Block['closed']))
759 | {
760 | return;
761 | }
762 |
763 | if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
764 | {
765 | $Block['depth'] ++;
766 | }
767 |
768 | if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
769 | {
770 | if ($Block['depth'] > 0)
771 | {
772 | $Block['depth'] --;
773 | }
774 | else
775 | {
776 | $Block['closed'] = true;
777 | }
778 | }
779 |
780 | if (isset($Block['interrupted']))
781 | {
782 | $Block['markup'] .= "\n";
783 |
784 | unset($Block['interrupted']);
785 | }
786 |
787 | $Block['markup'] .= "\n".$Line['body'];
788 |
789 | return $Block;
790 | }
791 |
792 | #
793 | # Reference
794 |
795 | protected function blockReference($Line)
796 | {
797 | if (preg_match('/^\[(.+?)\]:[ ]*(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
798 | {
799 | $id = strtolower($matches[1]);
800 |
801 | $Data = array(
802 | 'url' => $matches[2],
803 | 'title' => null,
804 | );
805 |
806 | if (isset($matches[3]))
807 | {
808 | $Data['title'] = $matches[3];
809 | }
810 |
811 | $this->DefinitionData['Reference'][$id] = $Data;
812 |
813 | $Block = array(
814 | 'hidden' => true,
815 | );
816 |
817 | return $Block;
818 | }
819 | }
820 |
821 | #
822 | # Table
823 |
824 | protected function blockTable($Line, array $Block = null)
825 | {
826 | if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
827 | {
828 | return;
829 | }
830 |
831 | if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
832 | {
833 | $alignments = array();
834 |
835 | $divider = $Line['text'];
836 |
837 | $divider = trim($divider);
838 | $divider = trim($divider, '|');
839 |
840 | $dividerCells = explode('|', $divider);
841 |
842 | foreach ($dividerCells as $dividerCell)
843 | {
844 | $dividerCell = trim($dividerCell);
845 |
846 | if ($dividerCell === '')
847 | {
848 | continue;
849 | }
850 |
851 | $alignment = null;
852 |
853 | if ($dividerCell[0] === ':')
854 | {
855 | $alignment = 'left';
856 | }
857 |
858 | if (substr($dividerCell, - 1) === ':')
859 | {
860 | $alignment = $alignment === 'left' ? 'center' : 'right';
861 | }
862 |
863 | $alignments []= $alignment;
864 | }
865 |
866 | # ~
867 |
868 | $HeaderElements = array();
869 |
870 | $header = $Block['element']['text'];
871 |
872 | $header = trim($header);
873 | $header = trim($header, '|');
874 |
875 | $headerCells = explode('|', $header);
876 |
877 | foreach ($headerCells as $index => $headerCell)
878 | {
879 | $headerCell = trim($headerCell);
880 |
881 | $HeaderElement = array(
882 | 'name' => 'th',
883 | 'text' => $headerCell,
884 | 'handler' => 'line',
885 | );
886 |
887 | if (isset($alignments[$index]))
888 | {
889 | $alignment = $alignments[$index];
890 |
891 | $HeaderElement['attributes'] = array(
892 | 'style' => 'text-align: '.$alignment.';',
893 | );
894 | }
895 |
896 | $HeaderElements []= $HeaderElement;
897 | }
898 |
899 | # ~
900 |
901 | $Block = array(
902 | 'alignments' => $alignments,
903 | 'identified' => true,
904 | 'element' => array(
905 | 'name' => 'table',
906 | 'handler' => 'elements',
907 | ),
908 | );
909 |
910 | $Block['element']['text'] []= array(
911 | 'name' => 'thead',
912 | 'handler' => 'elements',
913 | );
914 |
915 | $Block['element']['text'] []= array(
916 | 'name' => 'tbody',
917 | 'handler' => 'elements',
918 | 'text' => array(),
919 | );
920 |
921 | $Block['element']['text'][0]['text'] []= array(
922 | 'name' => 'tr',
923 | 'handler' => 'elements',
924 | 'text' => $HeaderElements,
925 | );
926 |
927 | return $Block;
928 | }
929 | }
930 |
931 | protected function blockTableContinue($Line, array $Block)
932 | {
933 | if (isset($Block['interrupted']))
934 | {
935 | return;
936 | }
937 |
938 | if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
939 | {
940 | $Elements = array();
941 |
942 | $row = $Line['text'];
943 |
944 | $row = trim($row);
945 | $row = trim($row, '|');
946 |
947 | preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
948 |
949 | foreach ($matches[0] as $index => $cell)
950 | {
951 | $cell = trim($cell);
952 |
953 | $Element = array(
954 | 'name' => 'td',
955 | 'handler' => 'line',
956 | 'text' => $cell,
957 | );
958 |
959 | if (isset($Block['alignments'][$index]))
960 | {
961 | $Element['attributes'] = array(
962 | 'style' => 'text-align: '.$Block['alignments'][$index].';',
963 | );
964 | }
965 |
966 | $Elements []= $Element;
967 | }
968 |
969 | $Element = array(
970 | 'name' => 'tr',
971 | 'handler' => 'elements',
972 | 'text' => $Elements,
973 | );
974 |
975 | $Block['element']['text'][1]['text'] []= $Element;
976 |
977 | return $Block;
978 | }
979 | }
980 |
981 | #
982 | # ~
983 | #
984 |
985 | protected function paragraph($Line)
986 | {
987 | $Block = array(
988 | 'element' => array(
989 | 'name' => 'p',
990 | 'text' => $Line['text'],
991 | 'handler' => 'line',
992 | ),
993 | );
994 |
995 | return $Block;
996 | }
997 |
998 | #
999 | # Inline Elements
1000 | #
1001 |
1002 | protected $InlineTypes = array(
1003 | '"' => array('SpecialCharacter'),
1004 | '!' => array('Image'),
1005 | '&' => array('SpecialCharacter'),
1006 | '*' => array('Emphasis'),
1007 | ':' => array('Url'),
1008 | '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
1009 | '>' => array('SpecialCharacter'),
1010 | '[' => array('Link'),
1011 | '_' => array('Emphasis'),
1012 | '`' => array('Code'),
1013 | '~' => array('Strikethrough'),
1014 | '\\' => array('EscapeSequence'),
1015 | );
1016 |
1017 | # ~
1018 |
1019 | protected $inlineMarkerList = '!"*_&[:<>`~\\';
1020 |
1021 | #
1022 | # ~
1023 | #
1024 |
1025 | public function line($text)
1026 | {
1027 | $markup = '';
1028 |
1029 | # $excerpt is based on the first occurrence of a marker
1030 |
1031 | while ($excerpt = strpbrk($text, $this->inlineMarkerList))
1032 | {
1033 | $marker = $excerpt[0];
1034 |
1035 | $markerPosition = strpos($text, $marker);
1036 |
1037 | $Excerpt = array('text' => $excerpt, 'context' => $text);
1038 |
1039 | foreach ($this->InlineTypes[$marker] as $inlineType)
1040 | {
1041 | $Inline = $this->{'inline'.$inlineType}($Excerpt);
1042 |
1043 | if ( ! isset($Inline))
1044 | {
1045 | continue;
1046 | }
1047 |
1048 | # makes sure that the inline belongs to "our" marker
1049 |
1050 | if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
1051 | {
1052 | continue;
1053 | }
1054 |
1055 | # sets a default inline position
1056 |
1057 | if ( ! isset($Inline['position']))
1058 | {
1059 | $Inline['position'] = $markerPosition;
1060 | }
1061 |
1062 | # the text that comes before the inline
1063 | $unmarkedText = substr($text, 0, $Inline['position']);
1064 |
1065 | # compile the unmarked text
1066 | $markup .= $this->unmarkedText($unmarkedText);
1067 |
1068 | # compile the inline
1069 | $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
1070 |
1071 | # remove the examined text
1072 | $text = substr($text, $Inline['position'] + $Inline['extent']);
1073 |
1074 | continue 2;
1075 | }
1076 |
1077 | # the marker does not belong to an inline
1078 |
1079 | $unmarkedText = substr($text, 0, $markerPosition + 1);
1080 |
1081 | $markup .= $this->unmarkedText($unmarkedText);
1082 |
1083 | $text = substr($text, $markerPosition + 1);
1084 | }
1085 |
1086 | $markup .= $this->unmarkedText($text);
1087 |
1088 | return $markup;
1089 | }
1090 |
1091 | #
1092 | # ~
1093 | #
1094 |
1095 | protected function inlineCode($Excerpt)
1096 | {
1097 | $marker = $Excerpt['text'][0];
1098 |
1099 | if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]),
1107 | 'element' => array(
1108 | 'name' => 'code',
1109 | 'text' => $text,
1110 | ),
1111 | );
1112 | }
1113 | }
1114 |
1115 | protected function inlineEmailTag($Excerpt)
1116 | {
1117 | if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
1118 | {
1119 | $url = $matches[1];
1120 |
1121 | if ( ! isset($matches[2]))
1122 | {
1123 | $url = 'mailto:' . $url;
1124 | }
1125 |
1126 | return array(
1127 | 'extent' => strlen($matches[0]),
1128 | 'element' => array(
1129 | 'name' => 'a',
1130 | 'text' => $matches[1],
1131 | 'attributes' => array(
1132 | 'href' => $url,
1133 | ),
1134 | ),
1135 | );
1136 | }
1137 | }
1138 |
1139 | protected function inlineEmphasis($Excerpt)
1140 | {
1141 | if ( ! isset($Excerpt['text'][1]))
1142 | {
1143 | return;
1144 | }
1145 |
1146 | $marker = $Excerpt['text'][0];
1147 |
1148 | if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
1149 | {
1150 | $emphasis = 'strong';
1151 | }
1152 | elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
1153 | {
1154 | $emphasis = 'em';
1155 | }
1156 | else
1157 | {
1158 | return;
1159 | }
1160 |
1161 | return array(
1162 | 'extent' => strlen($matches[0]),
1163 | 'element' => array(
1164 | 'name' => $emphasis,
1165 | 'handler' => 'line',
1166 | 'text' => $matches[1],
1167 | ),
1168 | );
1169 | }
1170 |
1171 | protected function inlineEscapeSequence($Excerpt)
1172 | {
1173 | if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
1174 | {
1175 | return array(
1176 | 'markup' => $Excerpt['text'][1],
1177 | 'extent' => 2,
1178 | );
1179 | }
1180 | }
1181 |
1182 | protected function inlineImage($Excerpt)
1183 | {
1184 | if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
1185 | {
1186 | return;
1187 | }
1188 |
1189 | $Excerpt['text']= substr($Excerpt['text'], 1);
1190 |
1191 | $Link = $this->inlineLink($Excerpt);
1192 |
1193 | if ($Link === null)
1194 | {
1195 | return;
1196 | }
1197 |
1198 | $Inline = array(
1199 | 'extent' => $Link['extent'] + 1,
1200 | 'element' => array(
1201 | 'name' => 'img',
1202 | 'attributes' => array(
1203 | 'src' => $Link['element']['attributes']['href'],
1204 | 'alt' => $Link['element']['text'],
1205 | ),
1206 | ),
1207 | );
1208 |
1209 | $Inline['element']['attributes'] += $Link['element']['attributes'];
1210 |
1211 | unset($Inline['element']['attributes']['href']);
1212 |
1213 | return $Inline;
1214 | }
1215 |
1216 | protected function inlineLink($Excerpt)
1217 | {
1218 | $Element = array(
1219 | 'name' => 'a',
1220 | 'handler' => 'line',
1221 | 'text' => null,
1222 | 'attributes' => array(
1223 | 'href' => null,
1224 | 'title' => null,
1225 | ),
1226 | );
1227 |
1228 | $extent = 0;
1229 |
1230 | $remainder = $Excerpt['text'];
1231 |
1232 | if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
1233 | {
1234 | $Element['text'] = $matches[1];
1235 |
1236 | $extent += strlen($matches[0]);
1237 |
1238 | $remainder = substr($remainder, $extent);
1239 | }
1240 | else
1241 | {
1242 | return;
1243 | }
1244 |
1245 | if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
1246 | {
1247 | $Element['attributes']['href'] = $matches[1];
1248 |
1249 | if (isset($matches[2]))
1250 | {
1251 | $Element['attributes']['title'] = substr($matches[2], 1, - 1);
1252 | }
1253 |
1254 | $extent += strlen($matches[0]);
1255 | }
1256 | else
1257 | {
1258 | if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
1259 | {
1260 | $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
1261 | $definition = strtolower($definition);
1262 |
1263 | $extent += strlen($matches[0]);
1264 | }
1265 | else
1266 | {
1267 | $definition = strtolower($Element['text']);
1268 | }
1269 |
1270 | if ( ! isset($this->DefinitionData['Reference'][$definition]))
1271 | {
1272 | return;
1273 | }
1274 |
1275 | $Definition = $this->DefinitionData['Reference'][$definition];
1276 |
1277 | $Element['attributes']['href'] = $Definition['url'];
1278 | $Element['attributes']['title'] = $Definition['title'];
1279 | }
1280 |
1281 | $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
1282 |
1283 | return array(
1284 | 'extent' => $extent,
1285 | 'element' => $Element,
1286 | );
1287 | }
1288 |
1289 | protected function inlineMarkup($Excerpt)
1290 | {
1291 | if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
1292 | {
1293 | return;
1294 | }
1295 |
1296 | if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
1297 | {
1298 | return array(
1299 | 'markup' => $matches[0],
1300 | 'extent' => strlen($matches[0]),
1301 | );
1302 | }
1303 |
1304 | if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches))
1305 | {
1306 | return array(
1307 | 'markup' => $matches[0],
1308 | 'extent' => strlen($matches[0]),
1309 | );
1310 | }
1311 |
1312 | if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
1313 | {
1314 | return array(
1315 | 'markup' => $matches[0],
1316 | 'extent' => strlen($matches[0]),
1317 | );
1318 | }
1319 | }
1320 |
1321 | protected function inlineSpecialCharacter($Excerpt)
1322 | {
1323 | if ($Excerpt['text'][0] === '&' and ! preg_match('/^?\w+;/', $Excerpt['text']))
1324 | {
1325 | return array(
1326 | 'markup' => '&',
1327 | 'extent' => 1,
1328 | );
1329 | }
1330 |
1331 | $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
1332 |
1333 | if (isset($SpecialCharacter[$Excerpt['text'][0]]))
1334 | {
1335 | return array(
1336 | 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
1337 | 'extent' => 1,
1338 | );
1339 | }
1340 | }
1341 |
1342 | protected function inlineStrikethrough($Excerpt)
1343 | {
1344 | if ( ! isset($Excerpt['text'][1]))
1345 | {
1346 | return;
1347 | }
1348 |
1349 | if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
1350 | {
1351 | return array(
1352 | 'extent' => strlen($matches[0]),
1353 | 'element' => array(
1354 | 'name' => 'del',
1355 | 'text' => $matches[1],
1356 | 'handler' => 'line',
1357 | ),
1358 | );
1359 | }
1360 | }
1361 |
1362 | protected function inlineUrl($Excerpt)
1363 | {
1364 | if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
1365 | {
1366 | return;
1367 | }
1368 |
1369 | if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
1370 | {
1371 | $Inline = array(
1372 | 'extent' => strlen($matches[0][0]),
1373 | 'position' => $matches[0][1],
1374 | 'element' => array(
1375 | 'name' => 'a',
1376 | 'text' => $matches[0][0],
1377 | 'attributes' => array(
1378 | 'href' => $matches[0][0],
1379 | ),
1380 | ),
1381 | );
1382 |
1383 | return $Inline;
1384 | }
1385 | }
1386 |
1387 | protected function inlineUrlTag($Excerpt)
1388 | {
1389 | if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
1390 | {
1391 | $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
1392 |
1393 | return array(
1394 | 'extent' => strlen($matches[0]),
1395 | 'element' => array(
1396 | 'name' => 'a',
1397 | 'text' => $url,
1398 | 'attributes' => array(
1399 | 'href' => $url,
1400 | ),
1401 | ),
1402 | );
1403 | }
1404 | }
1405 |
1406 | # ~
1407 |
1408 | protected function unmarkedText($text)
1409 | {
1410 | if ($this->breaksEnabled)
1411 | {
1412 | $text = preg_replace('/[ ]*\n/', "
\n", $text);
1413 | }
1414 | else
1415 | {
1416 | $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text);
1417 | $text = str_replace(" \n", "\n", $text);
1418 | }
1419 |
1420 | return $text;
1421 | }
1422 |
1423 | #
1424 | # Handlers
1425 | #
1426 |
1427 | protected function element(array $Element)
1428 | {
1429 | $markup = '<'.$Element['name'];
1430 |
1431 | if (isset($Element['attributes']))
1432 | {
1433 | foreach ($Element['attributes'] as $name => $value)
1434 | {
1435 | if ($value === null)
1436 | {
1437 | continue;
1438 | }
1439 |
1440 | $markup .= ' '.$name.'="'.$value.'"';
1441 | }
1442 | }
1443 |
1444 | if (isset($Element['text']))
1445 | {
1446 | $markup .= '>';
1447 |
1448 | if (isset($Element['handler']))
1449 | {
1450 | $markup .= $this->{$Element['handler']}($Element['text']);
1451 | }
1452 | else
1453 | {
1454 | $markup .= $Element['text'];
1455 | }
1456 |
1457 | $markup .= ''.$Element['name'].'>';
1458 | }
1459 | else
1460 | {
1461 | $markup .= ' />';
1462 | }
1463 |
1464 | return $markup;
1465 | }
1466 |
1467 | protected function elements(array $Elements)
1468 | {
1469 | $markup = '';
1470 |
1471 | foreach ($Elements as $Element)
1472 | {
1473 | $markup .= "\n" . $this->element($Element);
1474 | }
1475 |
1476 | $markup .= "\n";
1477 |
1478 | return $markup;
1479 | }
1480 |
1481 | # ~
1482 |
1483 | protected function li($lines)
1484 | {
1485 | $markup = $this->lines($lines);
1486 |
1487 | $trimmedMarkup = trim($markup);
1488 |
1489 | if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '')
1490 | {
1491 | $markup = $trimmedMarkup;
1492 | $markup = substr($markup, 3);
1493 |
1494 | $position = strpos($markup, "
");
1495 |
1496 | $markup = substr_replace($markup, '', $position, 4);
1497 | }
1498 |
1499 | return $markup;
1500 | }
1501 |
1502 | #
1503 | # Deprecated Methods
1504 | #
1505 |
1506 | function parse($text)
1507 | {
1508 | $markup = $this->text($text);
1509 |
1510 | return $markup;
1511 | }
1512 |
1513 | #
1514 | # Static Methods
1515 | #
1516 |
1517 | static function instance($name = 'default')
1518 | {
1519 | if (isset(self::$instances[$name]))
1520 | {
1521 | return self::$instances[$name];
1522 | }
1523 |
1524 | $instance = new static();
1525 |
1526 | self::$instances[$name] = $instance;
1527 |
1528 | return $instance;
1529 | }
1530 |
1531 | private static $instances = array();
1532 |
1533 | #
1534 | # Fields
1535 | #
1536 |
1537 | protected $DefinitionData;
1538 |
1539 | #
1540 | # Read-Only
1541 |
1542 | protected $specialCharacters = array(
1543 | '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
1544 | );
1545 |
1546 | protected $StrongRegex = array(
1547 | '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
1548 | '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
1549 | );
1550 |
1551 | protected $EmRegex = array(
1552 | '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
1553 | '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
1554 | );
1555 |
1556 | protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
1557 |
1558 | protected $voidElements = array(
1559 | 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
1560 | );
1561 |
1562 | protected $textLevelElements = array(
1563 | 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
1564 | 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
1565 | 'i', 'rp', 'del', 'code', 'strike', 'marquee',
1566 | 'q', 'rt', 'ins', 'font', 'strong',
1567 | 's', 'tt', 'kbd', 'mark',
1568 | 'u', 'xm', 'sub', 'nobr',
1569 | 'sup', 'ruby',
1570 | 'var', 'span',
1571 | 'wbr', 'time',
1572 | );
1573 | }
1574 |
1575 |
--------------------------------------------------------------------------------