170 lines
6.2 KiB
PHP
170 lines
6.2 KiB
PHP
|
<?php
|
||
|
class DText
|
||
|
{
|
||
|
static public function parse($str)
|
||
|
{
|
||
|
$state = ['newline'];
|
||
|
$result = '';
|
||
|
|
||
|
# Normalize newlines.
|
||
|
$str = trim($str);
|
||
|
$str = preg_replace([
|
||
|
'/(\r\n?)/',
|
||
|
'/\n{3,}/',
|
||
|
# Nuke spaces between newlines.
|
||
|
'/ *\n */',
|
||
|
], [
|
||
|
"\n",
|
||
|
"\n\n",
|
||
|
"\n",
|
||
|
], $str);
|
||
|
|
||
|
$str = htmlentities($str);
|
||
|
|
||
|
# Keep newline, use carriage return for split.
|
||
|
$str = str_replace("\n", "\n\r", $str);
|
||
|
|
||
|
$data = explode("\r", $str);
|
||
|
|
||
|
# Parse header and list first, line by line.
|
||
|
foreach ($data as $d) {
|
||
|
$result .= self::parseline($d, $state);
|
||
|
}
|
||
|
|
||
|
# Parse inline tags as a whole.
|
||
|
$result = self::parseinline($result);
|
||
|
|
||
|
return $result;
|
||
|
}
|
||
|
|
||
|
static public function parseinline($str)
|
||
|
{
|
||
|
# Short links subtitution:
|
||
|
$str = preg_replace_callback('/\[\[(.+?)\]\]/', function($m) { # [[title]] or [[title|label]] ;link to wiki
|
||
|
$data = explode('|', $m[1], 2);
|
||
|
$title = $data[0];
|
||
|
$label = isset($data[1]) ? $data[1] : $title;
|
||
|
return "<a href=\"/wiki/show?title=" . urlencode(html_entity_decode(str_replace(" ", "_", $title))) . "\">" . $label . "</a>";
|
||
|
}, $str);
|
||
|
$str = preg_replace_callback('/\{\{(.+?)\}\}/', function($m) { # {{post tags here}} ;search post with tags
|
||
|
return "<a href=\"/post?tags=" . urlencode(html_entity_decode($m[1])) . "\">" . $m[1] . "</a>";
|
||
|
}, $str);
|
||
|
|
||
|
# Miscellaneous single line tags subtitution.
|
||
|
$str = preg_replace([
|
||
|
'/\[b\](.+?)\[\/b\]/',
|
||
|
'/\[i\](.+?)\[\/i\]/',
|
||
|
'/(post #(\d+))/i',
|
||
|
'/(forum #(\d+))/i',
|
||
|
'/(comment #(\d+))/i',
|
||
|
'/(pool #(\d+))/i',
|
||
|
|
||
|
# Single line spoiler tags.
|
||
|
'/\[spoilers?\](.+?)\[\/spoilers?\]/',
|
||
|
'/\[spoilers?=(.+?)\](.+?)\[\/spoilers?\]/',
|
||
|
|
||
|
# Multi line spoiler tags.
|
||
|
'/\[spoilers?\]/',
|
||
|
'/\[spoilers?=(.+?)\]/',
|
||
|
'/\[\/spoilers?\]/',
|
||
|
|
||
|
# Quote.
|
||
|
'/\[quote\]/',
|
||
|
'/\[\/quote\]/',
|
||
|
], [
|
||
|
'<strong>\1</strong>',
|
||
|
'<em>\1</em>',
|
||
|
'<a href="/post/show/\2">\1</a>',
|
||
|
'<a href="/forum/show/\2">\1</a>',
|
||
|
'<a href="/comment/show/\2">\1</a>',
|
||
|
'<a href="/pool/show/\2">\1</a>',
|
||
|
|
||
|
'<span class="spoiler" onclick="Comment.spoiler(this); return false;"><span class="spoilerwarning">spoiler</span></span><span class="spoilertext" style="display: none">\1</span>',
|
||
|
'<span class="spoiler" onclick="Comment.spoiler(this); return false;"><span class="spoilerwarning">\1</span></span><span class="spoilertext" style="display: none">\2</span>',
|
||
|
|
||
|
'<span class="spoiler" onclick="Comment.spoiler(this); return false;"><span class="spoilerwarning">spoiler</span></span><div class="spoilertext" style="display: none">',
|
||
|
'<span class="spoiler" onclick="Comment.spoiler(this); return false;"><span class="spoilerwarning">\1</span></span><div class="spoilertext" style="display: none">',
|
||
|
'</div>',
|
||
|
|
||
|
'<blockquote><div>',
|
||
|
'</div></blockquote>',
|
||
|
], $str);
|
||
|
|
||
|
$str = self::parseurl($str);
|
||
|
|
||
|
return preg_replace([
|
||
|
# Extraneous newlines before closing div are unnecessary.
|
||
|
'/\n+(<\/div>)/',
|
||
|
# So are after headers, lists, and blockquotes.
|
||
|
'/(<\/(ul|h\d+|blockquote)>)\n+/',
|
||
|
# And after opening blockquote.
|
||
|
'/(<blockquote><div>)\n+/',
|
||
|
'/\n/',
|
||
|
], [
|
||
|
'\1',
|
||
|
'\1',
|
||
|
'\1',
|
||
|
'<br>',
|
||
|
], $str);
|
||
|
}
|
||
|
|
||
|
static public function parseline($str, &$state)
|
||
|
{
|
||
|
if (preg_match('/\d/', end($state)) || preg_match('/^\*+\s+/', $str)) {
|
||
|
return self::parselist($str, $state);
|
||
|
} elseif (preg_match('/^(h[1-6])\.\s*(.+)\n*/', $str, $m)) {
|
||
|
return "<${m[1]}>${m[2]}</${m[1]}>";
|
||
|
} else {
|
||
|
return $str;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static public function parselist($str, &$state)
|
||
|
{
|
||
|
$html = '';
|
||
|
if (!preg_match('/\d/', end($state))) {
|
||
|
$state[] = "1";
|
||
|
$html .= '<ul>';
|
||
|
} else {
|
||
|
$n = substr_count(preg_match('/^\*+\s+/', $str) ? $str : '', '*');
|
||
|
$last = (int)end($state);
|
||
|
if ($n < $last) {
|
||
|
$html .= str_repeat('</ul>', $last - $n);
|
||
|
array_pop($state);
|
||
|
$state[] = (string)$n;
|
||
|
} elseif ($n > $last) {
|
||
|
$html .= '<ul>';
|
||
|
array_pop($state);
|
||
|
$state[] = (string)(end($state) + 1);
|
||
|
}
|
||
|
if (!preg_match('/^\*+\s+/', $str)) {
|
||
|
array_pop($state);
|
||
|
return $html . self::parseline($str, $state);
|
||
|
}
|
||
|
}
|
||
|
$html .= preg_replace('/\*+\s+(.+)\n*/', '<li>\1', $str);
|
||
|
return $html;
|
||
|
}
|
||
|
|
||
|
static public function parseurl($str)
|
||
|
{
|
||
|
# Basic URL pattern
|
||
|
$url = '(h?ttps?:\/\/\[?(:{0,2}[\w\-]+)((:{1,2}|\.)[\w\-]+)*\]?(:\d+)*(\/[^\s\n<]*)*)';
|
||
|
|
||
|
# Substitute url tag in this form:
|
||
|
return preg_replace([
|
||
|
'/(^|[\s\(>])' . $url . '/', # url
|
||
|
'/<<\s*' . $url . '\s*\|\s*(.+?)\s*>>/', # <<url|label>>
|
||
|
'/(^|[\s>])"(.+?)":' . $url . '/', # "label":url
|
||
|
'/<<\s*' . $url . '\s*>>/', # <<url>>
|
||
|
'/<a href="ttp/', # Fix ttp(s) scheme
|
||
|
], [
|
||
|
'\1<a href="\2">\2</a>',
|
||
|
'<a href="\1">\7</a>',
|
||
|
'\1<a href="\3">\2</a>',
|
||
|
'<a href="\1">\1</a>',
|
||
|
'<a href="http',
|
||
|
], $str);
|
||
|
}
|
||
|
}
|