commit 76e05398ac22a20f0835bb314caeeb6da52f9790
Author: dankert <devnull@localhost>
Date: Fri, 8 Sep 2017 20:49:15 +0200
Es funktioniert schon vieles, nur der Thema ist noch nicht so ganz fertig
Diffstat:
11 files changed, 3456 insertions(+), 0 deletions(-)
diff --git a/bee/bee.css b/bee/bee.css
@@ -0,0 +1,3 @@
+body {
+ background:silver;
+}+
\ No newline at end of file
diff --git a/bee/bee.php b/bee/bee.php
@@ -0,0 +1,522 @@
+<?php
+
+define('DEVELOPMENT',true);
+define('HTML_EXTENSION','.html');
+
+$site = array(
+ 'title'=>'My new Blog',
+ 'keywords_dir'=>'tags',
+ 'category_dir'=>'category',
+ 'sites_dir'=>'site',
+ 'theme_dir'=>'theme',
+ 'html_extension'=>false,
+ 'http_caching'=>true,
+ 'theme'=>'default'
+);
+require('./parsedown/ParseDown.php'); // Markdown
+require('./spyc/Spyc.php'); // YAML
+
+define('SCRIPT',basename($_SERVER['SCRIPT_FILENAME']));
+
+define('SITES_DIR','../site');
+define('THEME_DIR','../theme');
+define('SLASH' ,'/');
+
+$querypath = explode('/',$_SERVER['PATH_INFO']);
+
+define('ROOT_UP',str_repeat('../',substr_count($_SERVER['PATH_INFO'],'/')));
+define('SITE_UP',str_repeat('../',substr_count($_SERVER['PATH_INFO'],'/')-3));
+
+ array_shift($querypath); // Remove first slash
+define('CMD' ,array_shift($querypath));
+define('SITE' ,array_shift($querypath));
+
+if (is_null(CMD))
+{
+ $sitelinks = '';
+ if ( $handle = opendir(SITES_DIR.SLASH.SITE.SLASH.implode('/',PATH)) )
+ {
+ while (false !== ($entry = readdir($handle))) {
+ if ( $entry[0] != '.' )
+ {
+ $sitelinks.= '<a href="'.ROOT_UP.SCRIPT.'/preview/'.$entry.'">'.$entry.'</a>';
+ }
+ }
+ closedir($handle);
+ }
+ $preview_link = '<a href="'.ROOT_UP.SCRIPT.'/preview'.'">Preview</a>';
+ output( 'Menu',$sitelinks);
+
+}
+elseif (CMD=='preview')
+{
+ if ( is_null(SITE) || SITE=='')
+ {
+ $sitelinks = '';
+ if ( $handle = opendir(SITES_DIR.SLASH.SITE.SLASH.implode('/',PATH)) )
+ {
+ while (false !== ($entry = readdir($handle))) {
+ if ( $entry[0] != '.' )
+ {
+ $sitelinks.= '<a href="'.ROOT_UP.SCRIPT.'/preview/'.$entry.'/">'.$entry.'</a>';
+ }
+ }
+ closedir($handle);
+ }
+ output( 'Sites',$sitelinks);
+ }
+ else
+ {
+ if (!is_dir(SITES_DIR.SLASH.SITE))
+ {
+ http(404,"Not found"); // Site does not exist
+ }
+
+ if ( is_file(SITES_DIR.SLASH.'site-config.ini'))
+ {
+ $siteconfig = parse_ini_file(SITES_DIR.SLASH.'site-config.ini');
+ if ( isset($siteconfig['site_dir']))
+ define('SITE_DIR',$siteconfig['site_dir']);
+ else
+ define('SITE_DIR',SITES_DIR.SLASH.SITE);
+ if ( isset($siteconfig['theme']))
+ define('THEME',$siteconfig['theme']);
+ else
+ define('THEME','default');
+ }
+ else
+ {
+ define('THEME','default');
+ define('SITE_DIR',SITES_DIR.SLASH.SITE);
+ }
+
+ if ( !is_file(THEME_DIR.SLASH.THEME.'.php') )
+ http(501,'Internal Server Error: Theme '.THEME.' not found'); // Theme does not exist
+
+
+ $pages = read_all_pages('');
+ $pages_by_url = array();
+ $pages_by_date = array();
+ $pages_by_keyword = array();
+
+ foreach( $pages as $page )
+ {
+ $url = $page['url'];
+ $i=1;
+
+ // Prüfung auf doppelte Dateinamen. Jeder Dateiname muss eindeutig sein. Ggf. wird ein Suffix ergänzt.
+ while( isset($pages_by_url[$url]) )
+ {
+ $url = $page['url'].'-'.++$i;
+ }
+ $pages_by_url[$url] = $page;
+ $page['url'] = $url;
+
+ $date = $page['date'];
+ if ( !isset($pages_by_date[$date]))
+ $pages_by_date[$date] = array();
+ $pages_by_date[$date][] = $page;
+
+ $keywords = $page['keywords'];
+ foreach( $keywords as $keyword ) {
+ if ( !isset($pages_by_keyword[$keyword]))
+ $pages_by_keyword[$keyword] = array();
+ $pages_by_keyword[$keyword][] = $page;
+ }
+
+ }
+
+
+ ksort($pages_by_date );
+ ksort($pages_by_keyword);
+ ksort($pages_by_url );
+
+ define('PAGES_BY_DATE' ,$pages_by_date );
+ define('PAGES_BY_KEYWORD',$pages_by_keyword);
+ define('PAGES_BY_URL' ,$pages_by_url );
+ define('KEYWORDS' ,array_keys($pages_by_keyword) );
+
+ $filename = implode(SLASH,$querypath);
+
+ define('PATH',$querypath);
+
+ // Pfad /tag für die Keywords
+ if (count($querypath)==2 && $querypath[0]=='tag' )
+ {
+ if ( !isset($querypath[1]))
+ http(410,'Bad request'); // Pfad '/tag' ohne weiteres
+
+ generate_keyword($querypath[1]);
+ }
+
+ // Directory-Listing
+ if (is_dir(SITES_DIR.SLASH.SITE.SLASH.$filename))
+ {
+ generate_directory();
+ }
+ // Static file from theme
+ elseif (is_file(THEME_DIR.SLASH.THEME.SLASH.$filename))
+ {
+ // Read a static file from theme (Images, CSS or JS)
+ lastModified(filemtime(THEME_DIR.SLASH.THEME.SLASH.$filename));
+ readfile(THEME_DIR.SLASH.THEME.SLASH.$filename);
+ }
+ // Static file from site
+ elseif (is_file(SITE_DIR.SLASH.$filename))
+ {
+ // Read a static file from site (Images, CSS or JS)
+ lastModified(filemtime(SITE_DIR.SLASH.$filename));
+ readfile(SITE_DIR.SLASH.$filename);
+ }
+ elseif (isset($pages_by_url[$filename]))
+ {
+ generate_markdown_page($pages_by_url[$filename]);
+ }
+ elseif (is_file(SITES_DIR.SLASH.SITE.SLASH.$filename.'.html'))
+ {
+ generate_html_page();
+ }
+ else {
+ http(404,'Not Found');
+ }
+
+ }
+}
+else {
+ http(400,'Unknown command');
+}
+
+
+function generate_directory()
+{
+ define('INDEX' ,true);
+ $article = '<div class="directory"';
+
+
+
+ foreach( array_reverse(PAGES_BY_DATE) as $pages )
+ {
+ foreach( $pages as $page)
+ {
+ if ( substr($page['filename'],0,strlen(implode('/',PATH))) == implode('/',PATH) )
+ $article .= '<div><a href="'.SITE_UP.$page['url'].'">'.$page['title'].'</a></div>';
+ }
+ }
+ $article .= '</div>';
+
+
+ define('ARTICLE',$article);
+ define('TITLE',implode('/',PATH));
+ define('UP' ,'../');
+ require( THEME_DIR.SLASH.THEME.'.php');
+ exit;
+}
+
+function generate_markdown_page($page)
+{
+ lastModified( $page['date'] );
+
+ $parsedown = new Parsedown();
+
+ $html = $parsedown->text( $page['value'] );
+
+ define('ARTICLE',$html);
+ define('INDEX' ,false);
+ define('TITLE' ,$page['title']);
+ define('UP' ,SITE_UP);
+ define('PAGE' ,$page);
+
+ $relatedPages = array();
+ foreach( $page['keywords'] as $keyword )
+ {
+ foreach( PAGES_BY_KEYWORD[$keyword] as $pagesPerKeyword)
+ {
+ foreach($pagesPerKeyword as $page)
+ {
+ if ( !isset($relatedPages[$page['url']]))
+ $relatedPages[$page['url']] = 1;
+ else
+ $relatedPages[$page['url']] = $relatedPages[$page['url']] + 1;
+ }
+ }
+ }
+ arsort($relatedPages);
+ $pages = array();
+ foreach( $relatedPages as $url)
+ {
+ $pages[] = PAGES_BY_URL[$url];
+ }
+ define('RELATED_PAGES',$pages);
+
+ if ( is_file(SITES_DIR.SLASH.SITE.'/site-config.ini'))
+ extract(parse_ini_file( SITES_DIR.SLASH.SITE.'/site-config.ini'),EXTR_PREFIX_ALL,'site');
+
+ require( THEME_DIR.SLASH.THEME.'.php');
+
+ exit;
+}
+
+function generate_html_page()
+{
+ $file = file(SITES_DIR.SLASH.SITE.SLASH.implode('/',PATH).'.html');
+
+ lastModified( filemtime(SITES_DIR.SLASH.SITE.SLASH.implode('/',PATH).'.html') );
+ $html = implode("\n",$file);
+ define('ARTICLE',$html);
+ define('INDEX' ,false);
+ define('TITLE' ,implode('/',PATH));
+ define('UP' ,SITE_UP);
+ require( THEME_DIR.SLASH.THEME.'.php');
+ exit;
+}
+
+
+function generate_keyword( $keyword )
+{
+ $html = '<ul>';
+ foreach( PAGES_BY_KEYWORD[$keyword] as $page )
+ {
+ $html.='<li><a href="'.SITE_UP.$page['url'].'">'.$page['config']['title'].'</a></li>';
+ }
+ $html .= '</ul>';
+ define('ARTICLE',$html);
+ define('INDEX' ,false);
+ define('TITLE' ,$keyword);
+ define('UP' ,'../');
+ require( THEME_DIR.SLASH.THEME.'.php');
+ exit;
+}
+
+
+
+function output( $title, $body )
+{
+ header('Content-Type: text/html');
+ ?>
+<html>
+<head>
+<title><?php echo $title ?></title>
+<link rel="stylesheet" href="<?php echo ROOT_UP ?>bee.css">
+</head>
+<body>
+<h1>Bee Site Generator - <?php echo $title ?></h1>
+<?php echo $body ?>
+</body></html>
+<?php
+ exit;
+}
+
+
+
+
+/**
+ * Schickt einen HTTP-Status zum Client und beendet das Skript.
+ *
+ * @param Integer $status HTTP-Status (ganzzahlig) (Default: 501)
+ * @param String $text HTTP-Meldung (Default: 'Internal Server Error')
+ */
+function http( $status=501,$text='Internal Server Error' )
+{
+ if ( headers_sent() )
+ {
+ echo "$status $text\n$message";
+ exit;
+ }
+
+ header('HTTP/1.0 '.intval($status).' '.$text);
+ header('Content-Type: text/html');
+ echo <<<HTML
+<html>
+<head><title>$status $text</title></head>
+<body>
+<h1>$text</h1>
+<hr>
+<address>Bee</adddress>
+</body>
+</html>
+HTML;
+ exit;
+}
+
+
+function explode_array($sep, $array)
+{
+ $idx = 0;
+ $target = array();
+ $target[$idx] = array();
+
+ foreach( $array as $line )
+ {
+ if ( trim($line) == $sep )
+ {
+ $target[++$idx] = array();
+ continue;
+ }
+ $target[$idx][] = $line;
+ }
+
+ return $target;
+}
+
+
+function read_page($filename)
+{
+ $file = file(SITE_DIR.SLASH.$filename);
+ $page = array();
+ $fileParts = explode_array('---',$file);
+ if (isset($fileParts[1]))
+ {
+ $pageYaml = spyc_load( implode("\n",$fileParts[1]));
+ $page['config'] = array_change_key_case($pageYaml);
+ }
+
+ if (isset($fileParts[2]))
+ {
+ $page['value'] = implode("\n",$fileParts[2]);
+ }
+ $page['filename' ] = $filename;
+ $page['filemtime'] = filemtime(SITE_DIR.SLASH.$filename);
+
+
+ if (isset($page['config']['date']))
+ $page['date'] = strtotime($page['config']['date']);
+ else
+ $page['date'] = $page['filemtime'];
+
+ if ( isset( $page['config']['tags'] ))
+ if ( is_array($page['config']['tags']) )
+ $page['keywords'] = urlify_array($page['config']['tags']);
+ else
+ $page['keywords'] = urlify_array(explode(',',$page['config']['tags']));
+ else
+ $page['keywords'] = array();
+
+
+
+ if (!empty($page['config']['url']))
+ if ( substr($page['config']['url'],0,1)=='/')
+ $page['url'] = urlify(substr($page['config']['url'],1));
+ else
+ $page['url'] = dirname($page['filename']).SLASH.urlify($page['config']['url']);
+ elseif (!empty($page['config']['title']))
+ {
+ if (!empty($page['config']['category']))
+ $page['url'] = urlify($page['config']['category']).SLASH.urlify($page['config']['title']);
+ else
+ $page['url'] = urlify($page['config']['title']);
+ }
+ else
+ $page['url'] = dirname($page['filename']).SLASH.urlify(substr(basename($page['filename']),0,-3));
+
+ if (isset($page['config']['title']))
+ $page['title'] = $page['config']['title'];
+ else
+ $page['title'] = basename($page['filename']);
+
+ if (isset($page['config']['author']))
+ $page['author'] = $page['config']['author'];
+ else
+ $page['author'] = posix_getpwuid(fileowner(SITE_DIR.SLASH.$filename))['name'];
+
+ return $page;
+}
+
+
+function read_all_pages( $dir )
+{
+ $pages = array();
+ if ( $handle = opendir(SITE_DIR.SLASH.$dir) )
+ {
+ while (false !== ($entry = readdir($handle)))
+ {
+ if ( $entry[0] == '.' )
+ continue;
+ if ( is_dir( SITE_DIR.SLASH.$dir.(!empty($dir)?SLASH:'').$entry ))
+ {
+ $pages = array_merge($pages,read_all_pages($dir.(!empty($dir)?SLASH:'').$entry));
+ }
+ if ( is_file( SITE_DIR.SLASH.$dir.(!empty($dir)?SLASH:'').$entry ) && substr($entry,-3)=='.md')
+ {
+ $page = read_page($dir.(!empty($dir)?SLASH:'').$entry);
+
+ if ( $page['date'] <= time() )
+ $pages[] = $page;
+ else
+ ; // pages in the future are not being published yet
+ }
+ }
+ closedir($handle);
+ }
+
+ return $pages;
+}
+
+
+
+
+
+function lastModified( $time )
+{
+
+ if ( DEVELOPMENT ) return;
+
+ // Conditional-Get eingeschaltet?
+ $lastModified = substr(date('r',$time -date('Z')),0,-5).'GMT';
+ $etag = '"'.md5($lastModified).'"';
+
+ // Header senden
+ header('Last-Modified: '.$lastModified );
+ header('ETag: ' .$etag );
+
+ // Die vom Interpreter sonst automatisch gesetzten
+ // Header uebersteuern
+ header('Cache-Control: must-revalidate');
+ header('Pragma:');
+
+ // See if the client has provided the required headers
+ $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) : false;
+ $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH'] ) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'] ) : false;
+
+ if ( !$if_modified_since && !$if_none_match )
+ return;
+
+ // At least one of the headers is there - check them
+ if ( $if_none_match && $if_none_match != $etag )
+ return; // etag is there but doesn't match
+
+ if ( $if_modified_since && $if_modified_since != $lastModified )
+ return; // if-modified-since is there but doesn't match
+
+ // Der entfernte Browser bzw. Proxy holt die Seite nun aus seinem Cache
+ header('HTTP/1.0 304 Not Modified');
+ exit; // Sofortiges Skript-Ende
+}
+
+
+
+/**
+ * Macht aus einem Namen eine korrekte, lesebare URL.
+ * @param $filename Name
+ * @return string
+ */
+function urlify( $filename )
+{
+ // thx https://stackoverflow.com/questions/2955251/php-function-to-make-slug-url-string
+ $slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $filename)));
+ return $slug;
+}
+
+
+function urlify_array( $nameArray )
+{
+ $newNames = array();
+ foreach( $nameArray as $name)
+ {
+ $newNames[] = urlify($name);
+ }
+ return $newNames;
+
+}
+
+
+?>+
\ No newline at end of file
diff --git a/bee/parsedown/ParseDown.php b/bee/parsedown/ParseDown.php
@@ -0,0 +1,1548 @@
+<?php
+
+#
+#
+# Parsedown
+# http://parsedown.org
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class Parsedown
+{
+ # ~
+
+ const version = '1.6.0';
+
+ # ~
+
+ function text($text)
+ {
+ # make sure no definitions are set
+ $this->DefinitionData = array();
+
+ # standardize line breaks
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+ # remove surrounding line breaks
+ $text = trim($text, "\n");
+
+ # split text into lines
+ $lines = explode("\n", $text);
+
+ # iterate through lines to identify blocks
+ $markup = $this->lines($lines);
+
+ # trim line breaks
+ $markup = trim($markup, "\n");
+
+ return $markup;
+ }
+
+ #
+ # Setters
+ #
+
+ function setBreaksEnabled($breaksEnabled)
+ {
+ $this->breaksEnabled = $breaksEnabled;
+
+ return $this;
+ }
+
+ protected $breaksEnabled;
+
+ function setMarkupEscaped($markupEscaped)
+ {
+ $this->markupEscaped = $markupEscaped;
+
+ return $this;
+ }
+
+ protected $markupEscaped;
+
+ function setUrlsLinked($urlsLinked)
+ {
+ $this->urlsLinked = $urlsLinked;
+
+ return $this;
+ }
+
+ protected $urlsLinked = true;
+
+ #
+ # Lines
+ #
+
+ protected $BlockTypes = array(
+ '#' => array('Header'),
+ '*' => array('Rule', 'List'),
+ '+' => array('List'),
+ '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+ '0' => array('List'),
+ '1' => array('List'),
+ '2' => array('List'),
+ '3' => array('List'),
+ '4' => array('List'),
+ '5' => array('List'),
+ '6' => array('List'),
+ '7' => array('List'),
+ '8' => array('List'),
+ '9' => array('List'),
+ ':' => array('Table'),
+ '<' => array('Comment', 'Markup'),
+ '=' => array('SetextHeader'),
+ '>' => array('Quote'),
+ '[' => array('Reference'),
+ '_' => array('Rule'),
+ '`' => array('FencedCode'),
+ '|' => array('Table'),
+ '~' => array('FencedCode'),
+ );
+
+ # ~
+
+ protected $unmarkedBlockTypes = array(
+ 'Code',
+ );
+
+ #
+ # Blocks
+ #
+
+ protected function lines(array $lines)
+ {
+ $CurrentBlock = null;
+
+ foreach ($lines as $line)
+ {
+ if (chop($line) === '')
+ {
+ if (isset($CurrentBlock))
+ {
+ $CurrentBlock['interrupted'] = true;
+ }
+
+ continue;
+ }
+
+ if (strpos($line, "\t") !== false)
+ {
+ $parts = explode("\t", $line);
+
+ $line = $parts[0];
+
+ unset($parts[0]);
+
+ foreach ($parts as $part)
+ {
+ $shortage = 4 - mb_strlen($line, 'utf-8') % 4;
+
+ $line .= str_repeat(' ', $shortage);
+ $line .= $part;
+ }
+ }
+
+ $indent = 0;
+
+ while (isset($line[$indent]) and $line[$indent] === ' ')
+ {
+ $indent ++;
+ }
+
+ $text = $indent > 0 ? substr($line, $indent) : $line;
+
+ # ~
+
+ $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']))
+ {
+ $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $CurrentBlock = $Block;
+
+ continue;
+ }
+ else
+ {
+ if ($this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+ }
+ }
+
+ # ~
+
+ $marker = $text[0];
+
+ # ~
+
+ $blockTypes = $this->unmarkedBlockTypes;
+
+ if (isset($this->BlockTypes[$marker]))
+ {
+ foreach ($this->BlockTypes[$marker] as $blockType)
+ {
+ $blockTypes []= $blockType;
+ }
+ }
+
+ #
+ # ~
+
+ foreach ($blockTypes as $blockType)
+ {
+ $Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
+
+ if (isset($Block))
+ {
+ $Block['type'] = $blockType;
+
+ if ( ! isset($Block['identified']))
+ {
+ $Blocks []= $CurrentBlock;
+
+ $Block['identified'] = true;
+ }
+
+ if ($this->isBlockContinuable($blockType))
+ {
+ $Block['continuable'] = true;
+ }
+
+ $CurrentBlock = $Block;
+
+ continue 2;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
+ {
+ $CurrentBlock['element']['text'] .= "\n".$text;
+ }
+ else
+ {
+ $Blocks []= $CurrentBlock;
+
+ $CurrentBlock = $this->paragraph($Line);
+
+ $CurrentBlock['identified'] = true;
+ }
+ }
+
+ # ~
+
+ if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+ {
+ $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
+ }
+
+ # ~
+
+ $Blocks []= $CurrentBlock;
+
+ unset($Blocks[0]);
+
+ # ~
+
+ $markup = '';
+
+ foreach ($Blocks as $Block)
+ {
+ if (isset($Block['hidden']))
+ {
+ continue;
+ }
+
+ $markup .= "\n";
+ $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
+ }
+
+ $markup .= "\n";
+
+ # ~
+
+ return $markup;
+ }
+
+ protected function isBlockContinuable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Continue');
+ }
+
+ protected function isBlockCompletable($Type)
+ {
+ return method_exists($this, 'block'.$Type.'Complete');
+ }
+
+ #
+ # Code
+
+ protected function blockCode($Line, $Block = null)
+ {
+ if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['indent'] >= 4)
+ {
+ $text = substr($Line['body'], 4);
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeContinue($Line, $Block)
+ {
+ if ($Line['indent'] >= 4)
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text']['text'] .= "\n";
+
+ $text = substr($Line['body'], 4);
+
+ $Block['element']['text']['text'] .= $text;
+
+ return $Block;
+ }
+ }
+
+ protected function blockCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Comment
+
+ protected function blockComment($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
+ {
+ $Block = array(
+ 'markup' => $Line['body'],
+ );
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockCommentContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ $Block['markup'] .= "\n" . $Line['body'];
+
+ if (preg_match('/-->$/', $Line['text']))
+ {
+ $Block['closed'] = true;
+ }
+
+ return $Block;
+ }
+
+ #
+ # Fenced Code
+
+ protected function blockFencedCode($Line)
+ {
+ if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
+ {
+ $Element = array(
+ 'name' => 'code',
+ 'text' => '',
+ );
+
+ if (isset($matches[1]))
+ {
+ $class = 'language-'.$matches[1];
+
+ $Element['attributes'] = array(
+ 'class' => $class,
+ );
+ }
+
+ $Block = array(
+ 'char' => $Line['text'][0],
+ 'element' => array(
+ 'name' => 'pre',
+ 'handler' => 'element',
+ 'text' => $Element,
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockFencedCodeContinue($Line, $Block)
+ {
+ if (isset($Block['complete']))
+ {
+ return;
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text']['text'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
+ {
+ $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
+
+ $Block['complete'] = true;
+
+ return $Block;
+ }
+
+ $Block['element']['text']['text'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ protected function blockFencedCodeComplete($Block)
+ {
+ $text = $Block['element']['text']['text'];
+
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+
+ $Block['element']['text']['text'] = $text;
+
+ return $Block;
+ }
+
+ #
+ # Header
+
+ protected function blockHeader($Line)
+ {
+ if (isset($Line['text'][1]))
+ {
+ $level = 1;
+
+ while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
+ {
+ $level ++;
+ }
+
+ if ($level > 6)
+ {
+ return;
+ }
+
+ $text = trim($Line['text'], '# ');
+
+ $Block = array(
+ 'element' => array(
+ 'name' => 'h' . min(6, $level),
+ 'text' => $text,
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # List
+
+ protected function blockList($Line)
+ {
+ list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
+
+ if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'indent' => $Line['indent'],
+ 'pattern' => $pattern,
+ 'element' => array(
+ 'name' => $name,
+ 'handler' => 'elements',
+ ),
+ );
+
+ if($name === 'ol')
+ {
+ $listStart = stristr($matches[0], '.', true);
+
+ if($listStart !== '1')
+ {
+ $Block['element']['attributes'] = array('start' => $listStart);
+ }
+ }
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $matches[2],
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+ }
+
+ protected function blockListContinue($Line, array $Block)
+ {
+ if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['li']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ unset($Block['li']);
+
+ $text = isset($matches[1]) ? $matches[1] : '';
+
+ $Block['li'] = array(
+ 'name' => 'li',
+ 'handler' => 'li',
+ 'text' => array(
+ $text,
+ ),
+ );
+
+ $Block['element']['text'] []= & $Block['li'];
+
+ return $Block;
+ }
+
+ if ($Line['text'][0] === '[' and $this->blockReference($Line))
+ {
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ return $Block;
+ }
+
+ if ($Line['indent'] > 0)
+ {
+ $Block['li']['text'] []= '';
+
+ $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
+
+ $Block['li']['text'] []= $text;
+
+ unset($Block['interrupted']);
+
+ return $Block;
+ }
+ }
+
+ #
+ # Quote
+
+ protected function blockQuote($Line)
+ {
+ if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'blockquote',
+ 'handler' => 'lines',
+ 'text' => (array) $matches[1],
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockQuoteContinue($Line, array $Block)
+ {
+ if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
+ {
+ if (isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= '';
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['element']['text'] []= $matches[1];
+
+ return $Block;
+ }
+
+ if ( ! isset($Block['interrupted']))
+ {
+ $Block['element']['text'] []= $Line['text'];
+
+ return $Block;
+ }
+ }
+
+ #
+ # Rule
+
+ protected function blockRule($Line)
+ {
+ if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'hr'
+ ),
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Setext
+
+ protected function blockSetextHeader($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (chop($Line['text'], $Line['text'][0]) === '')
+ {
+ $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+ return $Block;
+ }
+ }
+
+ #
+ # Markup
+
+ protected function blockMarkup($Line)
+ {
+ if ($this->markupEscaped)
+ {
+ return;
+ }
+
+ if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+ {
+ $element = strtolower($matches[1]);
+
+ if (in_array($element, $this->textLevelElements))
+ {
+ return;
+ }
+
+ $Block = array(
+ 'name' => $matches[1],
+ 'depth' => 0,
+ 'markup' => $Line['text'],
+ );
+
+ $length = strlen($matches[0]);
+
+ $remainder = substr($Line['text'], $length);
+
+ if (trim($remainder) === '')
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ $Block['closed'] = true;
+
+ $Block['void'] = true;
+ }
+ }
+ else
+ {
+ if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+ {
+ return;
+ }
+
+ if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ return $Block;
+ }
+ }
+
+ protected function blockMarkupContinue($Line, array $Block)
+ {
+ if (isset($Block['closed']))
+ {
+ return;
+ }
+
+ if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+ {
+ $Block['depth'] ++;
+ }
+
+ if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+ {
+ if ($Block['depth'] > 0)
+ {
+ $Block['depth'] --;
+ }
+ else
+ {
+ $Block['closed'] = true;
+ }
+ }
+
+ if (isset($Block['interrupted']))
+ {
+ $Block['markup'] .= "\n";
+
+ unset($Block['interrupted']);
+ }
+
+ $Block['markup'] .= "\n".$Line['body'];
+
+ return $Block;
+ }
+
+ #
+ # Reference
+
+ protected function blockReference($Line)
+ {
+ if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
+ {
+ $id = strtolower($matches[1]);
+
+ $Data = array(
+ 'url' => $matches[2],
+ 'title' => null,
+ );
+
+ if (isset($matches[3]))
+ {
+ $Data['title'] = $matches[3];
+ }
+
+ $this->DefinitionData['Reference'][$id] = $Data;
+
+ $Block = array(
+ 'hidden' => true,
+ );
+
+ return $Block;
+ }
+ }
+
+ #
+ # Table
+
+ protected function blockTable($Line, array $Block = null)
+ {
+ if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
+ {
+ $alignments = array();
+
+ $divider = $Line['text'];
+
+ $divider = trim($divider);
+ $divider = trim($divider, '|');
+
+ $dividerCells = explode('|', $divider);
+
+ foreach ($dividerCells as $dividerCell)
+ {
+ $dividerCell = trim($dividerCell);
+
+ if ($dividerCell === '')
+ {
+ continue;
+ }
+
+ $alignment = null;
+
+ if ($dividerCell[0] === ':')
+ {
+ $alignment = 'left';
+ }
+
+ if (substr($dividerCell, - 1) === ':')
+ {
+ $alignment = $alignment === 'left' ? 'center' : 'right';
+ }
+
+ $alignments []= $alignment;
+ }
+
+ # ~
+
+ $HeaderElements = array();
+
+ $header = $Block['element']['text'];
+
+ $header = trim($header);
+ $header = trim($header, '|');
+
+ $headerCells = explode('|', $header);
+
+ foreach ($headerCells as $index => $headerCell)
+ {
+ $headerCell = trim($headerCell);
+
+ $HeaderElement = array(
+ 'name' => 'th',
+ 'text' => $headerCell,
+ 'handler' => 'line',
+ );
+
+ if (isset($alignments[$index]))
+ {
+ $alignment = $alignments[$index];
+
+ $HeaderElement['attributes'] = array(
+ 'style' => 'text-align: '.$alignment.';',
+ );
+ }
+
+ $HeaderElements []= $HeaderElement;
+ }
+
+ # ~
+
+ $Block = array(
+ 'alignments' => $alignments,
+ 'identified' => true,
+ 'element' => array(
+ 'name' => 'table',
+ 'handler' => 'elements',
+ ),
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'thead',
+ 'handler' => 'elements',
+ );
+
+ $Block['element']['text'] []= array(
+ 'name' => 'tbody',
+ 'handler' => 'elements',
+ 'text' => array(),
+ );
+
+ $Block['element']['text'][0]['text'] []= array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $HeaderElements,
+ );
+
+ return $Block;
+ }
+ }
+
+ protected function blockTableContinue($Line, array $Block)
+ {
+ if (isset($Block['interrupted']))
+ {
+ return;
+ }
+
+ if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
+ {
+ $Elements = array();
+
+ $row = $Line['text'];
+
+ $row = trim($row);
+ $row = trim($row, '|');
+
+ preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
+
+ foreach ($matches[0] as $index => $cell)
+ {
+ $cell = trim($cell);
+
+ $Element = array(
+ 'name' => 'td',
+ 'handler' => 'line',
+ 'text' => $cell,
+ );
+
+ if (isset($Block['alignments'][$index]))
+ {
+ $Element['attributes'] = array(
+ 'style' => 'text-align: '.$Block['alignments'][$index].';',
+ );
+ }
+
+ $Elements []= $Element;
+ }
+
+ $Element = array(
+ 'name' => 'tr',
+ 'handler' => 'elements',
+ 'text' => $Elements,
+ );
+
+ $Block['element']['text'][1]['text'] []= $Element;
+
+ return $Block;
+ }
+ }
+
+ #
+ # ~
+ #
+
+ protected function paragraph($Line)
+ {
+ $Block = array(
+ 'element' => array(
+ 'name' => 'p',
+ 'text' => $Line['text'],
+ 'handler' => 'line',
+ ),
+ );
+
+ return $Block;
+ }
+
+ #
+ # Inline Elements
+ #
+
+ protected $InlineTypes = array(
+ '"' => array('SpecialCharacter'),
+ '!' => array('Image'),
+ '&' => array('SpecialCharacter'),
+ '*' => array('Emphasis'),
+ ':' => array('Url'),
+ '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
+ '>' => array('SpecialCharacter'),
+ '[' => array('Link'),
+ '_' => array('Emphasis'),
+ '`' => array('Code'),
+ '~' => array('Strikethrough'),
+ '\\' => array('EscapeSequence'),
+ );
+
+ # ~
+
+ protected $inlineMarkerList = '!"*_&[:<>`~\\';
+
+ #
+ # ~
+ #
+
+ public function line($text)
+ {
+ $markup = '';
+
+ # $excerpt is based on the first occurrence of a marker
+
+ while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+ {
+ $marker = $excerpt[0];
+
+ $markerPosition = strpos($text, $marker);
+
+ $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+ foreach ($this->InlineTypes[$marker] as $inlineType)
+ {
+ $Inline = $this->{'inline'.$inlineType}($Excerpt);
+
+ if ( ! isset($Inline))
+ {
+ continue;
+ }
+
+ # makes sure that the inline belongs to "our" marker
+
+ if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+ {
+ continue;
+ }
+
+ # sets a default inline position
+
+ if ( ! isset($Inline['position']))
+ {
+ $Inline['position'] = $markerPosition;
+ }
+
+ # the text that comes before the inline
+ $unmarkedText = substr($text, 0, $Inline['position']);
+
+ # compile the unmarked text
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ # compile the inline
+ $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
+
+ # remove the examined text
+ $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+ continue 2;
+ }
+
+ # the marker does not belong to an inline
+
+ $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+ $markup .= $this->unmarkedText($unmarkedText);
+
+ $text = substr($text, $markerPosition + 1);
+ }
+
+ $markup .= $this->unmarkedText($text);
+
+ return $markup;
+ }
+
+ #
+ # ~
+ #
+
+ protected function inlineCode($Excerpt)
+ {
+ $marker = $Excerpt['text'][0];
+
+ if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+ {
+ $text = $matches[2];
+ $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
+ $text = preg_replace("/[ ]*\n/", ' ', $text);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'code',
+ 'text' => $text,
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmailTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
+ {
+ $url = $matches[1];
+
+ if ( ! isset($matches[2]))
+ {
+ $url = 'mailto:' . $url;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[1],
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ protected function inlineEmphasis($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ $marker = $Excerpt['text'][0];
+
+ if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'strong';
+ }
+ elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+ {
+ $emphasis = 'em';
+ }
+ else
+ {
+ return;
+ }
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => $emphasis,
+ 'handler' => 'line',
+ 'text' => $matches[1],
+ ),
+ );
+ }
+
+ protected function inlineEscapeSequence($Excerpt)
+ {
+ if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+ {
+ return array(
+ 'markup' => $Excerpt['text'][1],
+ 'extent' => 2,
+ );
+ }
+ }
+
+ protected function inlineImage($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+ {
+ return;
+ }
+
+ $Excerpt['text']= substr($Excerpt['text'], 1);
+
+ $Link = $this->inlineLink($Excerpt);
+
+ if ($Link === null)
+ {
+ return;
+ }
+
+ $Inline = array(
+ 'extent' => $Link['extent'] + 1,
+ 'element' => array(
+ 'name' => 'img',
+ 'attributes' => array(
+ 'src' => $Link['element']['attributes']['href'],
+ 'alt' => $Link['element']['text'],
+ ),
+ ),
+ );
+
+ $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+ unset($Inline['element']['attributes']['href']);
+
+ return $Inline;
+ }
+
+ protected function inlineLink($Excerpt)
+ {
+ $Element = array(
+ 'name' => 'a',
+ 'handler' => 'line',
+ 'text' => null,
+ 'attributes' => array(
+ 'href' => null,
+ 'title' => null,
+ ),
+ );
+
+ $extent = 0;
+
+ $remainder = $Excerpt['text'];
+
+ if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+ {
+ $Element['text'] = $matches[1];
+
+ $extent += strlen($matches[0]);
+
+ $remainder = substr($remainder, $extent);
+ }
+ else
+ {
+ return;
+ }
+
+ if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
+ {
+ $Element['attributes']['href'] = $matches[1];
+
+ if (isset($matches[2]))
+ {
+ $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+ }
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+ {
+ $definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
+ $definition = strtolower($definition);
+
+ $extent += strlen($matches[0]);
+ }
+ else
+ {
+ $definition = strtolower($Element['text']);
+ }
+
+ if ( ! isset($this->DefinitionData['Reference'][$definition]))
+ {
+ return;
+ }
+
+ $Definition = $this->DefinitionData['Reference'][$definition];
+
+ $Element['attributes']['href'] = $Definition['url'];
+ $Element['attributes']['title'] = $Definition['title'];
+ }
+
+ $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
+
+ return array(
+ 'extent' => $extent,
+ 'element' => $Element,
+ );
+ }
+
+ protected function inlineMarkup($Excerpt)
+ {
+ if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+
+ if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'markup' => $matches[0],
+ 'extent' => strlen($matches[0]),
+ );
+ }
+ }
+
+ protected function inlineSpecialCharacter($Excerpt)
+ {
+ if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
+ {
+ return array(
+ 'markup' => '&',
+ 'extent' => 1,
+ );
+ }
+
+ $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
+
+ if (isset($SpecialCharacter[$Excerpt['text'][0]]))
+ {
+ return array(
+ 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
+ 'extent' => 1,
+ );
+ }
+ }
+
+ protected function inlineStrikethrough($Excerpt)
+ {
+ if ( ! isset($Excerpt['text'][1]))
+ {
+ return;
+ }
+
+ if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+ {
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'del',
+ 'text' => $matches[1],
+ 'handler' => 'line',
+ ),
+ );
+ }
+ }
+
+ protected function inlineUrl($Excerpt)
+ {
+ if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+ {
+ return;
+ }
+
+ if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
+ {
+ $Inline = array(
+ 'extent' => strlen($matches[0][0]),
+ 'position' => $matches[0][1],
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $matches[0][0],
+ 'attributes' => array(
+ 'href' => $matches[0][0],
+ ),
+ ),
+ );
+
+ return $Inline;
+ }
+ }
+
+ protected function inlineUrlTag($Excerpt)
+ {
+ if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
+ {
+ $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
+
+ return array(
+ 'extent' => strlen($matches[0]),
+ 'element' => array(
+ 'name' => 'a',
+ 'text' => $url,
+ 'attributes' => array(
+ 'href' => $url,
+ ),
+ ),
+ );
+ }
+ }
+
+ # ~
+
+ protected function unmarkedText($text)
+ {
+ if ($this->breaksEnabled)
+ {
+ $text = preg_replace('/[ ]*\n/', "<br />\n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text']);
+ }
+ else
+ {
+ $markup .= $Element['text'];
+ }
+
+ $markup .= '</'.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, "</p>");
+
+ $markup = substr_replace($markup, '', $position, 4);
+ }
+
+ return $markup;
+ }
+
+ #
+ # Deprecated Methods
+ #
+
+ function parse($text)
+ {
+ $markup = $this->text($text);
+
+ return $markup;
+ }
+
+ #
+ # Static Methods
+ #
+
+ static function instance($name = 'default')
+ {
+ if (isset(self::$instances[$name]))
+ {
+ return self::$instances[$name];
+ }
+
+ $instance = new static();
+
+ self::$instances[$name] = $instance;
+
+ return $instance;
+ }
+
+ private static $instances = array();
+
+ #
+ # Fields
+ #
+
+ protected $DefinitionData;
+
+ #
+ # Read-Only
+
+ protected $specialCharacters = array(
+ '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
+ );
+
+ protected $StrongRegex = array(
+ '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
+ '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
+ );
+
+ protected $EmRegex = array(
+ '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+ '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+ );
+
+ protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
+
+ protected $voidElements = array(
+ 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+ );
+
+ protected $textLevelElements = array(
+ 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+ 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+ 'i', 'rp', 'del', 'code', 'strike', 'marquee',
+ 'q', 'rt', 'ins', 'font', 'strong',
+ 's', 'tt', 'kbd', 'mark',
+ 'u', 'xm', 'sub', 'nobr',
+ 'sup', 'ruby',
+ 'var', 'span',
+ 'wbr', 'time',
+ );
+}
diff --git a/bee/parsedown/license.txt b/bee/parsedown/license.txt
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2013 Emanuil Rusev, erusev.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+
\ No newline at end of file
diff --git a/bee/spyc/Spyc.php b/bee/spyc/Spyc.php
@@ -0,0 +1,1161 @@
+<?php
+/**
+ * Spyc -- A Simple PHP YAML Class
+ * @version 0.6.2
+ * @author Vlad Andersen <vlad.andersen@gmail.com>
+ * @author Chris Wanstrath <chris@ozmm.org>
+ * @link https://github.com/mustangostang/spyc/
+ * @copyright Copyright 2005-2006 Chris Wanstrath, 2006-2011 Vlad Andersen
+ * @license http://www.opensource.org/licenses/mit-license.php MIT License
+ * @package Spyc
+ */
+
+if (!function_exists('spyc_load')) {
+ /**
+ * Parses YAML to array.
+ * @param string $string YAML string.
+ * @return array
+ */
+ function spyc_load ($string) {
+ return Spyc::YAMLLoadString($string);
+ }
+}
+
+if (!function_exists('spyc_load_file')) {
+ /**
+ * Parses YAML to array.
+ * @param string $file Path to YAML file.
+ * @return array
+ */
+ function spyc_load_file ($file) {
+ return Spyc::YAMLLoad($file);
+ }
+}
+
+if (!function_exists('spyc_dump')) {
+ /**
+ * Dumps array to YAML.
+ * @param array $data Array.
+ * @return string
+ */
+ function spyc_dump ($data) {
+ return Spyc::YAMLDump($data, false, false, true);
+ }
+}
+
+if (!class_exists('Spyc')) {
+
+/**
+ * The Simple PHP YAML Class.
+ *
+ * This class can be used to read a YAML file and convert its contents
+ * into a PHP array. It currently supports a very limited subsection of
+ * the YAML spec.
+ *
+ * Usage:
+ * <code>
+ * $Spyc = new Spyc;
+ * $array = $Spyc->load($file);
+ * </code>
+ * or:
+ * <code>
+ * $array = Spyc::YAMLLoad($file);
+ * </code>
+ * or:
+ * <code>
+ * $array = spyc_load_file($file);
+ * </code>
+ * @package Spyc
+ */
+class Spyc {
+
+ // SETTINGS
+
+ const REMPTY = "\0\0\0\0\0";
+
+ /**
+ * Setting this to true will force YAMLDump to enclose any string value in
+ * quotes. False by default.
+ *
+ * @var bool
+ */
+ public $setting_dump_force_quotes = false;
+
+ /**
+ * Setting this to true will forse YAMLLoad to use syck_load function when
+ * possible. False by default.
+ * @var bool
+ */
+ public $setting_use_syck_is_possible = false;
+
+
+
+ /**#@+
+ * @access private
+ * @var mixed
+ */
+ private $_dumpIndent;
+ private $_dumpWordWrap;
+ private $_containsGroupAnchor = false;
+ private $_containsGroupAlias = false;
+ private $path;
+ private $result;
+ private $LiteralPlaceHolder = '___YAML_Literal_Block___';
+ private $SavedGroups = array();
+ private $indent;
+ /**
+ * Path modifier that should be applied after adding current element.
+ * @var array
+ */
+ private $delayedPath = array();
+
+ /**#@+
+ * @access public
+ * @var mixed
+ */
+ public $_nodeId;
+
+/**
+ * Load a valid YAML string to Spyc.
+ * @param string $input
+ * @return array
+ */
+ public function load ($input) {
+ return $this->_loadString($input);
+ }
+
+ /**
+ * Load a valid YAML file to Spyc.
+ * @param string $file
+ * @return array
+ */
+ public function loadFile ($file) {
+ return $this->_load($file);
+ }
+
+ /**
+ * Load YAML into a PHP array statically
+ *
+ * The load method, when supplied with a YAML stream (string or file),
+ * will do its best to convert YAML in a file into a PHP array. Pretty
+ * simple.
+ * Usage:
+ * <code>
+ * $array = Spyc::YAMLLoad('lucky.yaml');
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input Path of YAML file or string containing YAML
+ */
+ public static function YAMLLoad($input) {
+ $Spyc = new Spyc;
+ return $Spyc->_load($input);
+ }
+
+ /**
+ * Load a string of YAML into a PHP array statically
+ *
+ * The load method, when supplied with a YAML string, will do its best
+ * to convert YAML in a string into a PHP array. Pretty simple.
+ *
+ * Note: use this function if you don't want files from the file system
+ * loaded and processed as YAML. This is of interest to people concerned
+ * about security whose input is from a string.
+ *
+ * Usage:
+ * <code>
+ * $array = Spyc::YAMLLoadString("---\n0: hello world\n");
+ * print_r($array);
+ * </code>
+ * @access public
+ * @return array
+ * @param string $input String containing YAML
+ */
+ public static function YAMLLoadString($input) {
+ $Spyc = new Spyc;
+ return $Spyc->_loadString($input);
+ }
+
+ /**
+ * Dump YAML from PHP array statically
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * save the returned string as nothing.yaml and pass it around.
+ *
+ * Oh, and you can decide how big the indent is and what the wordwrap
+ * for folding is. Pretty cool -- just pass in 'false' for either if
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @return string
+ * @param array|\stdClass $array PHP array
+ * @param int $indent Pass in false to use the default, which is 2
+ * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+ * @param bool $no_opening_dashes Do not start YAML file with "---\n"
+ */
+ public static function YAMLDump($array, $indent = false, $wordwrap = false, $no_opening_dashes = false) {
+ $spyc = new Spyc;
+ return $spyc->dump($array, $indent, $wordwrap, $no_opening_dashes);
+ }
+
+
+ /**
+ * Dump PHP array to YAML
+ *
+ * The dump method, when supplied with an array, will do its best
+ * to convert the array into friendly YAML. Pretty simple. Feel free to
+ * save the returned string as tasteful.yaml and pass it around.
+ *
+ * Oh, and you can decide how big the indent is and what the wordwrap
+ * for folding is. Pretty cool -- just pass in 'false' for either if
+ * you want to use the default.
+ *
+ * Indent's default is 2 spaces, wordwrap's default is 40 characters. And
+ * you can turn off wordwrap by passing in 0.
+ *
+ * @access public
+ * @return string
+ * @param array $array PHP array
+ * @param int $indent Pass in false to use the default, which is 2
+ * @param int $wordwrap Pass in 0 for no wordwrap, false for default (40)
+ */
+ public function dump($array,$indent = false,$wordwrap = false, $no_opening_dashes = false) {
+ // Dumps to some very clean YAML. We'll have to add some more features
+ // and options soon. And better support for folding.
+
+ // New features and options.
+ if ($indent === false or !is_numeric($indent)) {
+ $this->_dumpIndent = 2;
+ } else {
+ $this->_dumpIndent = $indent;
+ }
+
+ if ($wordwrap === false or !is_numeric($wordwrap)) {
+ $this->_dumpWordWrap = 40;
+ } else {
+ $this->_dumpWordWrap = $wordwrap;
+ }
+
+ // New YAML document
+ $string = "";
+ if (!$no_opening_dashes) $string = "---\n";
+
+ // Start at the base of the array and move through it.
+ if ($array) {
+ $array = (array)$array;
+ $previous_key = -1;
+ foreach ($array as $key => $value) {
+ if (!isset($first_key)) $first_key = $key;
+ $string .= $this->_yamlize($key,$value,0,$previous_key, $first_key, $array);
+ $previous_key = $key;
+ }
+ }
+ return $string;
+ }
+
+ /**
+ * Attempts to convert a key / value array item to YAML
+ * @access private
+ * @return string
+ * @param $key The name of the key
+ * @param $value The value of the item
+ * @param $indent The indent of the current node
+ */
+ private function _yamlize($key,$value,$indent, $previous_key = -1, $first_key = 0, $source_array = null) {
+ if(is_object($value)) $value = (array)$value;
+ if (is_array($value)) {
+ if (empty ($value))
+ return $this->_dumpNode($key, array(), $indent, $previous_key, $first_key, $source_array);
+ // It has children. What to do?
+ // Make it the right kind of item
+ $string = $this->_dumpNode($key, self::REMPTY, $indent, $previous_key, $first_key, $source_array);
+ // Add the indent
+ $indent += $this->_dumpIndent;
+ // Yamlize the array
+ $string .= $this->_yamlizeArray($value,$indent);
+ } elseif (!is_array($value)) {
+ // It doesn't have children. Yip.
+ $string = $this->_dumpNode($key, $value, $indent, $previous_key, $first_key, $source_array);
+ }
+ return $string;
+ }
+
+ /**
+ * Attempts to convert an array to YAML
+ * @access private
+ * @return string
+ * @param $array The array you want to convert
+ * @param $indent The indent of the current level
+ */
+ private function _yamlizeArray($array,$indent) {
+ if (is_array($array)) {
+ $string = '';
+ $previous_key = -1;
+ foreach ($array as $key => $value) {
+ if (!isset($first_key)) $first_key = $key;
+ $string .= $this->_yamlize($key, $value, $indent, $previous_key, $first_key, $array);
+ $previous_key = $key;
+ }
+ return $string;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Returns YAML from a key and a value
+ * @access private
+ * @return string
+ * @param $key The name of the key
+ * @param $value The value of the item
+ * @param $indent The indent of the current node
+ */
+ private function _dumpNode($key, $value, $indent, $previous_key = -1, $first_key = 0, $source_array = null) {
+ // do some folding here, for blocks
+ if (is_string ($value) && ((strpos($value,"\n") !== false || strpos($value,": ") !== false || strpos($value,"- ") !== false ||
+ strpos($value,"*") !== false || strpos($value,"#") !== false || strpos($value,"<") !== false || strpos($value,">") !== false || strpos ($value, '%') !== false || strpos ($value, ' ') !== false ||
+ strpos($value,"[") !== false || strpos($value,"]") !== false || strpos($value,"{") !== false || strpos($value,"}") !== false) || strpos($value,"&") !== false || strpos($value, "'") !== false || strpos($value, "!") === 0 ||
+ substr ($value, -1, 1) == ':')
+ ) {
+ $value = $this->_doLiteralBlock($value,$indent);
+ } else {
+ $value = $this->_doFolding($value,$indent);
+ }
+
+ if ($value === array()) $value = '[ ]';
+ if ($value === "") $value = '""';
+ if (self::isTranslationWord($value)) {
+ $value = $this->_doLiteralBlock($value, $indent);
+ }
+ if (trim ($value) != $value)
+ $value = $this->_doLiteralBlock($value,$indent);
+
+ if (is_bool($value)) {
+ $value = $value ? "true" : "false";
+ }
+
+ if ($value === null) $value = 'null';
+ if ($value === "'" . self::REMPTY . "'") $value = null;
+
+ $spaces = str_repeat(' ',$indent);
+
+ //if (is_int($key) && $key - 1 == $previous_key && $first_key===0) {
+ if (is_array ($source_array) && array_keys($source_array) === range(0, count($source_array) - 1)) {
+ // It's a sequence
+ $string = $spaces.'- '.$value."\n";
+ } else {
+ // if ($first_key===0) throw new Exception('Keys are all screwy. The first one was zero, now it\'s "'. $key .'"');
+ // It's mapped
+ if (strpos($key, ":") !== false || strpos($key, "#") !== false) { $key = '"' . $key . '"'; }
+ $string = rtrim ($spaces.$key.': '.$value)."\n";
+ }
+ return $string;
+ }
+
+ /**
+ * Creates a literal block for dumping
+ * @access private
+ * @return string
+ * @param $value
+ * @param $indent int The value of the indent
+ */
+ private function _doLiteralBlock($value,$indent) {
+ if ($value === "\n") return '\n';
+ if (strpos($value, "\n") === false && strpos($value, "'") === false) {
+ return sprintf ("'%s'", $value);
+ }
+ if (strpos($value, "\n") === false && strpos($value, '"') === false) {
+ return sprintf ('"%s"', $value);
+ }
+ $exploded = explode("\n",$value);
+ $newValue = '|';
+ if (isset($exploded[0]) && ($exploded[0] == "|" || $exploded[0] == "|-" || $exploded[0] == ">")) {
+ $newValue = $exploded[0];
+ unset($exploded[0]);
+ }
+ $indent += $this->_dumpIndent;
+ $spaces = str_repeat(' ',$indent);
+ foreach ($exploded as $line) {
+ $line = trim($line);
+ if (strpos($line, '"') === 0 && strrpos($line, '"') == (strlen($line)-1) || strpos($line, "'") === 0 && strrpos($line, "'") == (strlen($line)-1)) {
+ $line = substr($line, 1, -1);
+ }
+ $newValue .= "\n" . $spaces . ($line);
+ }
+ return $newValue;
+ }
+
+ /**
+ * Folds a string of text, if necessary
+ * @access private
+ * @return string
+ * @param $value The string you wish to fold
+ */
+ private function _doFolding($value,$indent) {
+ // Don't do anything if wordwrap is set to 0
+
+ if ($this->_dumpWordWrap !== 0 && is_string ($value) && strlen($value) > $this->_dumpWordWrap) {
+ $indent += $this->_dumpIndent;
+ $indent = str_repeat(' ',$indent);
+ $wrapped = wordwrap($value,$this->_dumpWordWrap,"\n$indent");
+ $value = ">\n".$indent.$wrapped;
+ } else {
+ if ($this->setting_dump_force_quotes && is_string ($value) && $value !== self::REMPTY)
+ $value = '"' . $value . '"';
+ if (is_numeric($value) && is_string($value))
+ $value = '"' . $value . '"';
+ }
+
+
+ return $value;
+ }
+
+ private function isTrueWord($value) {
+ $words = self::getTranslations(array('true', 'on', 'yes', 'y'));
+ return in_array($value, $words, true);
+ }
+
+ private function isFalseWord($value) {
+ $words = self::getTranslations(array('false', 'off', 'no', 'n'));
+ return in_array($value, $words, true);
+ }
+
+ private function isNullWord($value) {
+ $words = self::getTranslations(array('null', '~'));
+ return in_array($value, $words, true);
+ }
+
+ private function isTranslationWord($value) {
+ return (
+ self::isTrueWord($value) ||
+ self::isFalseWord($value) ||
+ self::isNullWord($value)
+ );
+ }
+
+ /**
+ * Coerce a string into a native type
+ * Reference: http://yaml.org/type/bool.html
+ * TODO: Use only words from the YAML spec.
+ * @access private
+ * @param $value The value to coerce
+ */
+ private function coerceValue(&$value) {
+ if (self::isTrueWord($value)) {
+ $value = true;
+ } else if (self::isFalseWord($value)) {
+ $value = false;
+ } else if (self::isNullWord($value)) {
+ $value = null;
+ }
+ }
+
+ /**
+ * Given a set of words, perform the appropriate translations on them to
+ * match the YAML 1.1 specification for type coercing.
+ * @param $words The words to translate
+ * @access private
+ */
+ private static function getTranslations(array $words) {
+ $result = array();
+ foreach ($words as $i) {
+ $result = array_merge($result, array(ucfirst($i), strtoupper($i), strtolower($i)));
+ }
+ return $result;
+ }
+
+// LOADING FUNCTIONS
+
+ private function _load($input) {
+ $Source = $this->loadFromSource($input);
+ return $this->loadWithSource($Source);
+ }
+
+ private function _loadString($input) {
+ $Source = $this->loadFromString($input);
+ return $this->loadWithSource($Source);
+ }
+
+ private function loadWithSource($Source) {
+ if (empty ($Source)) return array();
+ if ($this->setting_use_syck_is_possible && function_exists ('syck_load')) {
+ $array = syck_load (implode ("\n", $Source));
+ return is_array($array) ? $array : array();
+ }
+
+ $this->path = array();
+ $this->result = array();
+
+ $cnt = count($Source);
+ for ($i = 0; $i < $cnt; $i++) {
+ $line = $Source[$i];
+
+ $this->indent = strlen($line) - strlen(ltrim($line));
+ $tempPath = $this->getParentPathByIndent($this->indent);
+ $line = self::stripIndent($line, $this->indent);
+ if (self::isComment($line)) continue;
+ if (self::isEmpty($line)) continue;
+ $this->path = $tempPath;
+
+ $literalBlockStyle = self::startsLiteralBlock($line);
+ if ($literalBlockStyle) {
+ $line = rtrim ($line, $literalBlockStyle . " \n");
+ $literalBlock = '';
+ $line .= ' '.$this->LiteralPlaceHolder;
+ $literal_block_indent = strlen($Source[$i+1]) - strlen(ltrim($Source[$i+1]));
+ while (++$i < $cnt && $this->literalBlockContinues($Source[$i], $this->indent)) {
+ $literalBlock = $this->addLiteralLine($literalBlock, $Source[$i], $literalBlockStyle, $literal_block_indent);
+ }
+ $i--;
+ }
+
+ // Strip out comments
+ if (strpos ($line, '#')) {
+ $line = preg_replace('/\s*#([^"\']+)$/','',$line);
+ }
+
+ while (++$i < $cnt && self::greedilyNeedNextLine($line)) {
+ $line = rtrim ($line, " \n\t\r") . ' ' . ltrim ($Source[$i], " \t");
+ }
+ $i--;
+
+ $lineArray = $this->_parseLine($line);
+
+ if ($literalBlockStyle)
+ $lineArray = $this->revertLiteralPlaceHolder ($lineArray, $literalBlock);
+
+ $this->addArray($lineArray, $this->indent);
+
+ foreach ($this->delayedPath as $indent => $delayedPath)
+ $this->path[$indent] = $delayedPath;
+
+ $this->delayedPath = array();
+
+ }
+ return $this->result;
+ }
+
+ private function loadFromSource ($input) {
+ if (!empty($input) && strpos($input, "\n") === false && file_exists($input))
+ $input = file_get_contents($input);
+
+ return $this->loadFromString($input);
+ }
+
+ private function loadFromString ($input) {
+ $lines = explode("\n",$input);
+ foreach ($lines as $k => $_) {
+ $lines[$k] = rtrim ($_, "\r");
+ }
+ return $lines;
+ }
+
+ /**
+ * Parses YAML code and returns an array for a node
+ * @access private
+ * @return array
+ * @param string $line A line from the YAML file
+ */
+ private function _parseLine($line) {
+ if (!$line) return array();
+ $line = trim($line);
+ if (!$line) return array();
+
+ $array = array();
+
+ $group = $this->nodeContainsGroup($line);
+ if ($group) {
+ $this->addGroup($line, $group);
+ $line = $this->stripGroup ($line, $group);
+ }
+
+ if ($this->startsMappedSequence($line))
+ return $this->returnMappedSequence($line);
+
+ if ($this->startsMappedValue($line))
+ return $this->returnMappedValue($line);
+
+ if ($this->isArrayElement($line))
+ return $this->returnArrayElement($line);
+
+ if ($this->isPlainArray($line))
+ return $this->returnPlainArray($line);
+
+
+ return $this->returnKeyValuePair($line);
+
+ }
+
+ /**
+ * Finds the type of the passed value, returns the value as the new type.
+ * @access private
+ * @param string $value
+ * @return mixed
+ */
+ private function _toType($value) {
+ if ($value === '') return "";
+ $first_character = $value[0];
+ $last_character = substr($value, -1, 1);
+
+ $is_quoted = false;
+ do {
+ if (!$value) break;
+ if ($first_character != '"' && $first_character != "'") break;
+ if ($last_character != '"' && $last_character != "'") break;
+ $is_quoted = true;
+ } while (0);
+
+ if ($is_quoted) {
+ $value = str_replace('\n', "\n", $value);
+ if ($first_character == "'")
+ return strtr(substr ($value, 1, -1), array ('\'\'' => '\'', '\\\''=> '\''));
+ return strtr(substr ($value, 1, -1), array ('\\"' => '"', '\\\''=> '\''));
+ }
+
+ if (strpos($value, ' #') !== false && !$is_quoted)
+ $value = preg_replace('/\s+#(.+)$/','',$value);
+
+ if ($first_character == '[' && $last_character == ']') {
+ // Take out strings sequences and mappings
+ $innerValue = trim(substr ($value, 1, -1));
+ if ($innerValue === '') return array();
+ $explode = $this->_inlineEscape($innerValue);
+ // Propagate value array
+ $value = array();
+ foreach ($explode as $v) {
+ $value[] = $this->_toType($v);
+ }
+ return $value;
+ }
+
+ if (strpos($value,': ')!==false && $first_character != '{') {
+ $array = explode(': ',$value);
+ $key = trim($array[0]);
+ array_shift($array);
+ $value = trim(implode(': ',$array));
+ $value = $this->_toType($value);
+ return array($key => $value);
+ }
+
+ if ($first_character == '{' && $last_character == '}') {
+ $innerValue = trim(substr ($value, 1, -1));
+ if ($innerValue === '') return array();
+ // Inline Mapping
+ // Take out strings sequences and mappings
+ $explode = $this->_inlineEscape($innerValue);
+ // Propagate value array
+ $array = array();
+ foreach ($explode as $v) {
+ $SubArr = $this->_toType($v);
+ if (empty($SubArr)) continue;
+ if (is_array ($SubArr)) {
+ $array[key($SubArr)] = $SubArr[key($SubArr)]; continue;
+ }
+ $array[] = $SubArr;
+ }
+ return $array;
+ }
+
+ if ($value == 'null' || $value == 'NULL' || $value == 'Null' || $value == '' || $value == '~') {
+ return null;
+ }
+
+ if ( is_numeric($value) && preg_match ('/^(-|)[1-9]+[0-9]*$/', $value) ){
+ $intvalue = (int)$value;
+ if ($intvalue != PHP_INT_MAX && $intvalue != ~PHP_INT_MAX)
+ $value = $intvalue;
+ return $value;
+ }
+
+ if ( is_string($value) && preg_match('/^0[xX][0-9a-fA-F]+$/', $value)) {
+ // Hexadecimal value.
+ return hexdec($value);
+ }
+
+ $this->coerceValue($value);
+
+ if (is_numeric($value)) {
+ if ($value === '0') return 0;
+ if (rtrim ($value, 0) === $value)
+ $value = (float)$value;
+ return $value;
+ }
+
+ return $value;
+ }
+
+ /**
+ * Used in inlines to check for more inlines or quoted strings
+ * @access private
+ * @return array
+ */
+ private function _inlineEscape($inline) {
+ // There's gotta be a cleaner way to do this...
+ // While pure sequences seem to be nesting just fine,
+ // pure mappings and mappings with sequences inside can't go very
+ // deep. This needs to be fixed.
+
+ $seqs = array();
+ $maps = array();
+ $saved_strings = array();
+ $saved_empties = array();
+
+ // Check for empty strings
+ $regex = '/("")|(\'\')/';
+ if (preg_match_all($regex,$inline,$strings)) {
+ $saved_empties = $strings[0];
+ $inline = preg_replace($regex,'YAMLEmpty',$inline);
+ }
+ unset($regex);
+
+ // Check for strings
+ $regex = '/(?:(")|(?:\'))((?(1)[^"]+|[^\']+))(?(1)"|\')/';
+ if (preg_match_all($regex,$inline,$strings)) {
+ $saved_strings = $strings[0];
+ $inline = preg_replace($regex,'YAMLString',$inline);
+ }
+ unset($regex);
+
+ // echo $inline;
+
+ $i = 0;
+ do {
+
+ // Check for sequences
+ while (preg_match('/\[([^{}\[\]]+)\]/U',$inline,$matchseqs)) {
+ $seqs[] = $matchseqs[0];
+ $inline = preg_replace('/\[([^{}\[\]]+)\]/U', ('YAMLSeq' . (count($seqs) - 1) . 's'), $inline, 1);
+ }
+
+ // Check for mappings
+ while (preg_match('/{([^\[\]{}]+)}/U',$inline,$matchmaps)) {
+ $maps[] = $matchmaps[0];
+ $inline = preg_replace('/{([^\[\]{}]+)}/U', ('YAMLMap' . (count($maps) - 1) . 's'), $inline, 1);
+ }
+
+ if ($i++ >= 10) break;
+
+ } while (strpos ($inline, '[') !== false || strpos ($inline, '{') !== false);
+
+ $explode = explode(',',$inline);
+ $explode = array_map('trim', $explode);
+ $stringi = 0; $i = 0;
+
+ while (1) {
+
+ // Re-add the sequences
+ if (!empty($seqs)) {
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLSeq') !== false) {
+ foreach ($seqs as $seqk => $seq) {
+ $explode[$key] = str_replace(('YAMLSeq'.$seqk.'s'),$seq,$value);
+ $value = $explode[$key];
+ }
+ }
+ }
+ }
+
+ // Re-add the mappings
+ if (!empty($maps)) {
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLMap') !== false) {
+ foreach ($maps as $mapk => $map) {
+ $explode[$key] = str_replace(('YAMLMap'.$mapk.'s'), $map, $value);
+ $value = $explode[$key];
+ }
+ }
+ }
+ }
+
+
+ // Re-add the strings
+ if (!empty($saved_strings)) {
+ foreach ($explode as $key => $value) {
+ while (strpos($value,'YAMLString') !== false) {
+ $explode[$key] = preg_replace('/YAMLString/',$saved_strings[$stringi],$value, 1);
+ unset($saved_strings[$stringi]);
+ ++$stringi;
+ $value = $explode[$key];
+ }
+ }
+ }
+
+
+ // Re-add the empties
+ if (!empty($saved_empties)) {
+ foreach ($explode as $key => $value) {
+ while (strpos($value,'YAMLEmpty') !== false) {
+ $explode[$key] = preg_replace('/YAMLEmpty/', '', $value, 1);
+ $value = $explode[$key];
+ }
+ }
+ }
+
+ $finished = true;
+ foreach ($explode as $key => $value) {
+ if (strpos($value,'YAMLSeq') !== false) {
+ $finished = false; break;
+ }
+ if (strpos($value,'YAMLMap') !== false) {
+ $finished = false; break;
+ }
+ if (strpos($value,'YAMLString') !== false) {
+ $finished = false; break;
+ }
+ if (strpos($value,'YAMLEmpty') !== false) {
+ $finished = false; break;
+ }
+ }
+ if ($finished) break;
+
+ $i++;
+ if ($i > 10)
+ break; // Prevent infinite loops.
+ }
+
+
+ return $explode;
+ }
+
+ private function literalBlockContinues ($line, $lineIndent) {
+ if (!trim($line)) return true;
+ if (strlen($line) - strlen(ltrim($line)) > $lineIndent) return true;
+ return false;
+ }
+
+ private function referenceContentsByAlias ($alias) {
+ do {
+ if (!isset($this->SavedGroups[$alias])) { echo "Bad group name: $alias."; break; }
+ $groupPath = $this->SavedGroups[$alias];
+ $value = $this->result;
+ foreach ($groupPath as $k) {
+ $value = $value[$k];
+ }
+ } while (false);
+ return $value;
+ }
+
+ private function addArrayInline ($array, $indent) {
+ $CommonGroupPath = $this->path;
+ if (empty ($array)) return false;
+
+ foreach ($array as $k => $_) {
+ $this->addArray(array($k => $_), $indent);
+ $this->path = $CommonGroupPath;
+ }
+ return true;
+ }
+
+ private function addArray ($incoming_data, $incoming_indent) {
+
+ // print_r ($incoming_data);
+
+ if (count ($incoming_data) > 1)
+ return $this->addArrayInline ($incoming_data, $incoming_indent);
+
+ $key = key ($incoming_data);
+ $value = isset($incoming_data[$key]) ? $incoming_data[$key] : null;
+ if ($key === '__!YAMLZero') $key = '0';
+
+ if ($incoming_indent == 0 && !$this->_containsGroupAlias && !$this->_containsGroupAnchor) { // Shortcut for root-level values.
+ if ($key || $key === '' || $key === '0') {
+ $this->result[$key] = $value;
+ } else {
+ $this->result[] = $value; end ($this->result); $key = key ($this->result);
+ }
+ $this->path[$incoming_indent] = $key;
+ return;
+ }
+
+
+
+ $history = array();
+ // Unfolding inner array tree.
+ $history[] = $_arr = $this->result;
+ foreach ($this->path as $k) {
+ $history[] = $_arr = $_arr[$k];
+ }
+
+ if ($this->_containsGroupAlias) {
+ $value = $this->referenceContentsByAlias($this->_containsGroupAlias);
+ $this->_containsGroupAlias = false;
+ }
+
+
+ // Adding string or numeric key to the innermost level or $this->arr.
+ if (is_string($key) && $key == '<<') {
+ if (!is_array ($_arr)) { $_arr = array (); }
+
+ $_arr = array_merge ($_arr, $value);
+ } else if ($key || $key === '' || $key === '0') {
+ if (!is_array ($_arr))
+ $_arr = array ($key=>$value);
+ else
+ $_arr[$key] = $value;
+ } else {
+ if (!is_array ($_arr)) { $_arr = array ($value); $key = 0; }
+ else { $_arr[] = $value; end ($_arr); $key = key ($_arr); }
+ }
+
+ $reverse_path = array_reverse($this->path);
+ $reverse_history = array_reverse ($history);
+ $reverse_history[0] = $_arr;
+ $cnt = count($reverse_history) - 1;
+ for ($i = 0; $i < $cnt; $i++) {
+ $reverse_history[$i+1][$reverse_path[$i]] = $reverse_history[$i];
+ }
+ $this->result = $reverse_history[$cnt];
+
+ $this->path[$incoming_indent] = $key;
+
+ if ($this->_containsGroupAnchor) {
+ $this->SavedGroups[$this->_containsGroupAnchor] = $this->path;
+ if (is_array ($value)) {
+ $k = key ($value);
+ if (!is_int ($k)) {
+ $this->SavedGroups[$this->_containsGroupAnchor][$incoming_indent + 2] = $k;
+ }
+ }
+ $this->_containsGroupAnchor = false;
+ }
+
+ }
+
+ private static function startsLiteralBlock ($line) {
+ $lastChar = substr (trim($line), -1);
+ if ($lastChar != '>' && $lastChar != '|') return false;
+ if ($lastChar == '|') return $lastChar;
+ // HTML tags should not be counted as literal blocks.
+ if (preg_match ('#<.*?>$#', $line)) return false;
+ return $lastChar;
+ }
+
+ private static function greedilyNeedNextLine($line) {
+ $line = trim ($line);
+ if (!strlen($line)) return false;
+ if (substr ($line, -1, 1) == ']') return false;
+ if ($line[0] == '[') return true;
+ if (preg_match ('#^[^:]+?:\s*\[#', $line)) return true;
+ return false;
+ }
+
+ private function addLiteralLine ($literalBlock, $line, $literalBlockStyle, $indent = -1) {
+ $line = self::stripIndent($line, $indent);
+ if ($literalBlockStyle !== '|') {
+ $line = self::stripIndent($line);
+ }
+ $line = rtrim ($line, "\r\n\t ") . "\n";
+ if ($literalBlockStyle == '|') {
+ return $literalBlock . $line;
+ }
+ if (strlen($line) == 0)
+ return rtrim($literalBlock, ' ') . "\n";
+ if ($line == "\n" && $literalBlockStyle == '>') {
+ return rtrim ($literalBlock, " \t") . "\n";
+ }
+ if ($line != "\n")
+ $line = trim ($line, "\r\n ") . " ";
+ return $literalBlock . $line;
+ }
+
+ function revertLiteralPlaceHolder ($lineArray, $literalBlock) {
+ foreach ($lineArray as $k => $_) {
+ if (is_array($_))
+ $lineArray[$k] = $this->revertLiteralPlaceHolder ($_, $literalBlock);
+ else if (substr($_, -1 * strlen ($this->LiteralPlaceHolder)) == $this->LiteralPlaceHolder)
+ $lineArray[$k] = rtrim ($literalBlock, " \r\n");
+ }
+ return $lineArray;
+ }
+
+ private static function stripIndent ($line, $indent = -1) {
+ if ($indent == -1) $indent = strlen($line) - strlen(ltrim($line));
+ return substr ($line, $indent);
+ }
+
+ private function getParentPathByIndent ($indent) {
+ if ($indent == 0) return array();
+ $linePath = $this->path;
+ do {
+ end($linePath); $lastIndentInParentPath = key($linePath);
+ if ($indent <= $lastIndentInParentPath) array_pop ($linePath);
+ } while ($indent <= $lastIndentInParentPath);
+ return $linePath;
+ }
+
+
+ private function clearBiggerPathValues ($indent) {
+
+
+ if ($indent == 0) $this->path = array();
+ if (empty ($this->path)) return true;
+
+ foreach ($this->path as $k => $_) {
+ if ($k > $indent) unset ($this->path[$k]);
+ }
+
+ return true;
+ }
+
+
+ private static function isComment ($line) {
+ if (!$line) return false;
+ if ($line[0] == '#') return true;
+ if (trim($line, " \r\n\t") == '---') return true;
+ return false;
+ }
+
+ private static function isEmpty ($line) {
+ return (trim ($line) === '');
+ }
+
+
+ private function isArrayElement ($line) {
+ if (!$line || !is_scalar($line)) return false;
+ if (substr($line, 0, 2) != '- ') return false;
+ if (strlen ($line) > 3)
+ if (substr($line,0,3) == '---') return false;
+
+ return true;
+ }
+
+ private function isHashElement ($line) {
+ return strpos($line, ':');
+ }
+
+ private function isLiteral ($line) {
+ if ($this->isArrayElement($line)) return false;
+ if ($this->isHashElement($line)) return false;
+ return true;
+ }
+
+
+ private static function unquote ($value) {
+ if (!$value) return $value;
+ if (!is_string($value)) return $value;
+ if ($value[0] == '\'') return trim ($value, '\'');
+ if ($value[0] == '"') return trim ($value, '"');
+ return $value;
+ }
+
+ private function startsMappedSequence ($line) {
+ return (substr($line, 0, 2) == '- ' && substr ($line, -1, 1) == ':');
+ }
+
+ private function returnMappedSequence ($line) {
+ $array = array();
+ $key = self::unquote(trim(substr($line,1,-1)));
+ $array[$key] = array();
+ $this->delayedPath = array(strpos ($line, $key) + $this->indent => $key);
+ return array($array);
+ }
+
+ private function checkKeysInValue($value) {
+ if (strchr('[{"\'', $value[0]) === false) {
+ if (strchr($value, ': ') !== false) {
+ throw new Exception('Too many keys: '.$value);
+ }
+ }
+ }
+
+ private function returnMappedValue ($line) {
+ $this->checkKeysInValue($line);
+ $array = array();
+ $key = self::unquote (trim(substr($line,0,-1)));
+ $array[$key] = '';
+ return $array;
+ }
+
+ private function startsMappedValue ($line) {
+ return (substr ($line, -1, 1) == ':');
+ }
+
+ private function isPlainArray ($line) {
+ return ($line[0] == '[' && substr ($line, -1, 1) == ']');
+ }
+
+ private function returnPlainArray ($line) {
+ return $this->_toType($line);
+ }
+
+ private function returnKeyValuePair ($line) {
+ $array = array();
+ $key = '';
+ if (strpos ($line, ': ')) {
+ // It's a key/value pair most likely
+ // If the key is in double quotes pull it out
+ if (($line[0] == '"' || $line[0] == "'") && preg_match('/^(["\'](.*)["\'](\s)*:)/',$line,$matches)) {
+ $value = trim(str_replace($matches[1],'',$line));
+ $key = $matches[2];
+ } else {
+ // Do some guesswork as to the key and the value
+ $explode = explode(': ', $line);
+ $key = trim(array_shift($explode));
+ $value = trim(implode(': ', $explode));
+ $this->checkKeysInValue($value);
+ }
+ // Set the type of the value. Int, string, etc
+ $value = $this->_toType($value);
+ if ($key === '0') $key = '__!YAMLZero';
+ $array[$key] = $value;
+ } else {
+ $array = array ($line);
+ }
+ return $array;
+
+ }
+
+
+ private function returnArrayElement ($line) {
+ if (strlen($line) <= 1) return array(array()); // Weird %)
+ $array = array();
+ $value = trim(substr($line,1));
+ $value = $this->_toType($value);
+ if ($this->isArrayElement($value)) {
+ $value = $this->returnArrayElement($value);
+ }
+ $array[] = $value;
+ return $array;
+ }
+
+
+ private function nodeContainsGroup ($line) {
+ $symbolsForReference = 'A-z0-9_\-';
+ if (strpos($line, '&') === false && strpos($line, '*') === false) return false; // Please die fast ;-)
+ if ($line[0] == '&' && preg_match('/^(&['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
+ if ($line[0] == '*' && preg_match('/^(\*['.$symbolsForReference.']+)/', $line, $matches)) return $matches[1];
+ if (preg_match('/(&['.$symbolsForReference.']+)$/', $line, $matches)) return $matches[1];
+ if (preg_match('/(\*['.$symbolsForReference.']+$)/', $line, $matches)) return $matches[1];
+ if (preg_match ('#^\s*<<\s*:\s*(\*[^\s]+).*$#', $line, $matches)) return $matches[1];
+ return false;
+
+ }
+
+ private function addGroup ($line, $group) {
+ if ($group[0] == '&') $this->_containsGroupAnchor = substr ($group, 1);
+ if ($group[0] == '*') $this->_containsGroupAlias = substr ($group, 1);
+ //print_r ($this->path);
+ }
+
+ private function stripGroup ($line, $group) {
+ $line = trim(str_replace($group, '', $line));
+ return $line;
+ }
+}
+}
+
+// Enable use of Spyc from command line
+// The syntax is the following: php Spyc.php spyc.yaml
+
+do {
+ if (PHP_SAPI != 'cli') break;
+ if (empty ($_SERVER['argc']) || $_SERVER['argc'] < 2) break;
+ if (empty ($_SERVER['PHP_SELF']) || FALSE === strpos ($_SERVER['PHP_SELF'], 'Spyc.php') ) break;
+ $file = $argv[1];
+ echo json_encode (spyc_load_file ($file));
+} while (0);
diff --git a/create.sh b/create.sh
@@ -0,0 +1,40 @@
+#!/bin/bash
+#
+
+
+TITLE=$1
+if [ -z "$TITLE" ]; then
+ TITLE=New
+fi
+
+UTC=`date --rfc-3339=seconds`
+FILEPATH=site/default/`date +%Y`/`date +%m`
+
+
+
+FILE=$FILEPATH/$TITLE.md
+
+if [ -f $FILE ]; then
+ echo "$FILE existiert bereits"
+ exit 4
+fi
+echo "Lege an $FILE"
+
+mkdir -p $FILEPATH
+echo "---" >> $FILE
+echo "Title: $TITLE" >> $FILE
+echo "Date: $UTC" >> $FILE
+echo "Keywords: $TITLE" >> $FILE
+echo "Category: $TITLE" >> $FILE
+echo "Url:" >> $FILE
+echo "Author: " >> $FILE
+
+echo "---" >> $FILE
+echo "" >> $FILE
+echo "" >> $FILE
+
+
+
+"${EDITOR:-vi}" $FILE
+
+echo "Thank you".+
\ No newline at end of file
diff --git a/index.php b/index.php
@@ -0,0 +1,3 @@
+<?php
+header('Location: '.'./bee/bee.php');
+?>+
\ No newline at end of file
diff --git a/site/default/site-config.ini b/site/default/site-config.ini
@@ -0,0 +1,4 @@
+
+title = Bee Static Site generator
+theme = default
+site_lang_related_pages=Ähnliche Seiten+
\ No newline at end of file
diff --git a/theme/default.php b/theme/default.php
@@ -0,0 +1,105 @@
+<?php
+$lang = array(
+ 'recent_pages'=>'Recent Pages',
+ 'related_pages'=>'Related Pages',
+ 'keywords'=>'Keywords',
+ 'categories'=>'Categories',
+ 'date_format'=>'d.m.Y \u\m G:i \U\h\r'
+);
+extract($lang,EXTR_PREFIX_ALL,'site_lang');
+
+?><html lang="de">
+<head>
+ <title><?php echo TITLE ?> - <?php echo $site_title ?></title>
+ <meta content="text/html; charset=UTF-8" http-equiv="content-type" />
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="stylesheet" href="<?php echo SITE_UP ?>default.css">
+</head>
+<body>
+<header id="name">
+<h1><?php echo TITLE ?></h1>
+</header>
+
+<article>
+
+<span class="date"><?php echo date($site_lang_date_format,PAGE['date']) ?></span>
+<span class="author"><?php echo PAGE['author'] ?></span>
+<span class="category"><?php echo PAGE['author'] ?></span>
+<span class="keywords"><?php foreach( PAGE['keywords'] as $keyword ) { ?><a href="<?php echo UP.$keyword ?>"><?php echo $keyword ?></a><?php } ?></span>
+
+<?php echo ARTICLE ?>
+</article>
+
+<nav id="related">
+<header>
+<h1><?php echo $site_lang_related_pages ?></h1>
+</header>
+<ul>
+<?php foreach( PAGES_RELATED as $page )
+ {?>
+ <li><a href="<?php $page['url'] ?>">
+ <span class="date"><?php echo date('r',$page['date']) ?></span>
+ <span class="title"><?php echo $page['title'] ?></span>
+ </a></li>
+ <?php } ?>
+</ul>
+
+</nav>
+
+
+
+
+<nav id="recent">
+<header>
+<h1><?php $site_lang_recent_pages ?></h1>
+</header>
+<ul>
+<?php $count=0; foreach( PAGES_BY_DATE as $page)
+ { if ($count++ > 10) break; ?>
+ <li>
+ <span class="date"><?php echo date('r',$page['date']) ?></span>
+ <span class="title"><?php echo $page['title'] ?></span>
+ </li>
+ <?php } ?>
+</ul>
+
+</nav>
+
+
+<nav id="keywords">
+<header>
+<h1><?php $site_lang_keywords ?></h1>
+</header>
+<?php $count=0; foreach( KEYWORDS as $keyword)
+ { ?>
+ <span style="font-size:1em;"><a href="<?php echo UP.$keyword ?>"><?php echo $keyword ?></a>
+ </span>
+ <?php } ?>
+
+</nav>
+
+
+
+<nav id="categories">
+<header>
+<h1><?php $site_lang_categories ?></h1>
+</header>
+<?php $count=0; foreach( KEYWORDS as $keyword)
+ { ?>
+ <span style="font-size:1em;"><a href="<?php echo UP.'tag/'.$keyword ?>"><?php echo $keyword ?></a>
+ </span>
+ <?php } ?>
+
+</nav>
+
+
+<footer>
+
+<ul>
+<li><a href="<?php echo UP ?>datenschutz">Datenschutz</a></li>
+<li><a href="<?php echo UP ?>impressum">Impressum</a></li>
+</ul>
+
+</footer>
+
+</body>+
\ No newline at end of file
diff --git a/theme/default/default.css b/theme/default/default.css
@@ -0,0 +1,42 @@
+html {
+ line-height: 1.15; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+article,
+aside,
+footer,
+header,
+nav,
+section {
+ display: block;
+}
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+a {
+ background-color: transparent; /* 1 */
+ -webkit-text-decoration-skip: objects; /* 2 */
+}
+
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont,
+ "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
+ "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ font-size:13px; margin:0px;
+
+ background-color: DimGray;
+ color:white;
+ width: 100%;
+ max-width:95em;
+ padding:1em;
+}
+
+html { width:100%; box-sizing: border-box; }
+*, ::before, ::after { box-sizing: inherit; }
diff --git a/theme/default/lang_de.php b/theme/default/lang_de.php
@@ -0,0 +1 @@
+<?php