diff --git a/playground/index.php b/playground/index.php index a933707..7514cac 100644 --- a/playground/index.php +++ b/playground/index.php @@ -186,16 +186,175 @@ 1 -# parkdown playground +# Parkdown +– a simple recursive descent Markdown parser for PHP *(version >= 8.1)* - + -* use markdown in the editor -* view result over there +## Specification -View parkdown code [here][code]. +### Index +* [Block types](#supported_block_types) +* [Inline types](#supported_inline_types) +* [Examples](#examples) + * [Paragraphs](#paragraphs) + * [Images](#images) + * [Horizontal Rules](#horizontal_rules) + * [Block quotes](#block_quotes) + * [Code blocks](#code_blocks) + * [Tables](#tables) + * [References](#references) +* [Usage](#usage) +* [Testing](#testing) -[code]: https://git.mike-ochmann.de/MassiveDynamic/Parkdown +### Supported block types +Parkdown currently support the following block types: + +* codeblocks *(with the ability to specify a language for the code block)* +* tables *(with alignment specification)* +* paragraphs +* block quotes +* lists *(like this one)* + * also nested +* horizontal rules `---` + +### Supported inline types +Parkdown currently support the following block types: + +* bold text (`**bold**`) +* italic text (`*italic*`) +* code snippets +* images (``) +* links (`[link text][url or reference]`) + +### Additional functionality + +* references (`[marker]: URL`) + +## Examples +### Paragraphs +```markdown +A simple paragraph can contain **bold text**, `inline codeblocks` and *italic text*. We can also link [with a direct url][https://google.com] *(i.e. to google)* +or via reference to [a later defined url][massivedynamic], if we so desire. +``` + +A simple paragraph can contain **bold text**, `inline codeblocks` and *italic text*. We can also link [with a direct url](https://google.com) *(i.e. to google)* +or via reference to [a later defined url][massivedynamic], if we so desire. + +Paragraphs can be annotated with `id` and `class` attributes: + +```markdown +Paragraphs can be annotated with ids and classes {.thisIsAClass, .anotherClass, #thisIsAnID} +``` + +results in + +Paragraphs can be annotated with ids and classes {.thisIsAClass, .anotherClass, #thisIsAnID} + +```html + + Paragraphs can be annotated with ids and classes + +``` + +### Images +```markdown + +``` + + + +### Horizontal rules +```markdown +--- +``` + +--- +### Block quotes +```markdown +> Only two things are infinite, +> the universe and human stupidity, +> i am not totally shure about the universe, though... +> – Albert Einstein +``` + + +> Only two things are infinite, +> the universe and human stupidity, +> i am not totally shure about the universe, though... +> – Albert Einstein + +### Code blocks +```markdown + +\`\`\`php + function main(int $argc, array $argv) : int { + echo "Hello World!"; + + return 0; + } +\`\`\` + +``` + +```php + function main(int $argc, array $argv) : int { + echo "Hello World!"; + + return 0; + } +``` + +### Tables +```markdown +| Product name | Amount | Price | +|--------------|:--------:|-------:| +| Football | 7 | $18,00 | +| Golfball | 122 | $7,00 | +| Fooseball | 355 | $1,00 | +| Puck | 58 | $12,00 | +``` + +| Product name | Amount | Price | +|--------------|:--------:|-------:| +| Football | 7 | $18,00 | +| Golfball | 122 | $7,00 | +| Fooseball | 355 | $1,00 | +| Puck | 58 | $12,00 | + +### References + +```markdown +[massivedynamic]: https://massivedynamic.eu +``` + +[massivedynamic]: https://massivedynamic.eu + +## Usage +Simply construct an new `parkdown\Parkdown` object and pass the Markdown source code to it's constructor. The parsed `DOMDocument` or it's `HTML` output can then be retrieved through the `::html()` and `::tree()` member functions. + +**Example** + +```php + use parkdown\Parkdown; + + $source = " + This is a **bold** word in a paragraph. + "; + + $parser = new Parkdown($source); + $tree = $parser->tree(); + + print_r($tree); + echo $parser->html(); +``` + +## Testing +Unit tests can be run via `composer`: + +``` + composer test +``` diff --git a/src/Parser.php b/src/Parser.php index c62d606..6655ef7 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -83,6 +83,14 @@ class Parser { assert($assertion, new ParserError(self::LOC($token->location).$message)); } + public static function TextToSlug(string $html) : string { + $out = trim(strip_tags($html)); + $out = strtolower($out); + $out = str_replace(" ", "_", $out); + + return $out; + } + private function resolveReferences(DOMElement $node) : void { if (count($this->references) < 1) return; @@ -173,7 +181,7 @@ class Parser { $backtick = $this->consume(); self::Assert($backtick->type === TokenType::BACKTICK, $backtick, "inline code expression not autmatically closed (expected backtick)"); - return $this->document->createElement("code", $buffer); + return @$this->document->createElement("code", $buffer); } private function parseLink() : ?DOMNode { @@ -246,7 +254,11 @@ class Parser { continue; } elseif ($this->current()->type === TokenType::BACKTICK) { $clearBuffer(); - array_push($elms, $this->parseCode()); + $code = $this->parseCode(); + + self::Assert($code !== false, $this->current(), "malformed code block"); + + array_push($elms, $code); continue; } elseif ($this->current()->type === TokenType::LBRACKET) { $links = $this->parseLink(); @@ -364,13 +376,16 @@ class Parser { break; // then we expect an asterisk or a number followed by a period if ($type === ListType::UNORDERED) { - $asterisk = $this->consume(); - self::Assert($asterisk->type === TokenType::ASTERISK, $asterisk, "expected asterisk, got ".$asterisk->type->name); + if ($this->current()->type === TokenType::ASTERISK) + $this->consume(); } else { - $number = $this->consume(); - self::Assert($number->type === TokenType::NUMBER, $number, "expected number, got ".$number->type->name); - $period = $this->consume(); - self::Assert($period->type === TokenType::DOT, $period, "expected period, got ".$period->type->name); + if ($this->current()->type === TokenType::NUMBER) { + $this->consume(); + if ($this->strict && $this->current()->type !== TokenType::DOT) + $this->insert(new Token(TokenType::DOT, ".", $this->current()->location)); + $period = $this->consume(); + self::Assert($period->type === TokenType::DOT, $period, "expected period, got ".$period->type->name); + } } // then we parse the node content @@ -444,6 +459,7 @@ class Parser { foreach ($this->parseText() as $node) if ($node instanceof DOMNode) $elm->appendChild($node); + $elm->setAttribute("id", self::TextToSlug($elm->textContent)); $this->document->appendChild($elm); }
+ Paragraphs can be annotated with ids and classes +