diff --git a/README.md b/README.md index 3d8a796..5f45d7a 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,22 @@ or via reference to [a later defined url][massivedynamic], i 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], i 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  diff --git a/src/Lexer.php b/src/Lexer.php index 00bf586..9a386fd 100644 --- a/src/Lexer.php +++ b/src/Lexer.php @@ -104,6 +104,14 @@ class Lexer { $clearBuffer(); array_push($tokens, new Token(TokenType::COLON, $char)); break; + case '{': + $clearBuffer(); + array_push($tokens, new Token(TokenType::LBRACE, $char)); + break; + case '}': + $clearBuffer(); + array_push($tokens, new Token(TokenType::RBRACE, $char)); + break; default: $buffer .= $char; break; diff --git a/src/Parser.php b/src/Parser.php index 426254c..11ab17e 100644 --- a/src/Parser.php +++ b/src/Parser.php @@ -2,6 +2,7 @@ namespace parkdown; +use Attribute; use DOMDocument; use DOMElement; use DOMNode; @@ -11,6 +12,11 @@ enum ListType { case UNORDERED; } +class Attributes { + public array $classes = []; + public ?string $id = null; +} + class Parser { const MAGIC_CHAR = "*"; @@ -148,8 +154,8 @@ class Parser { } private function parseText($paragraph = false) : array { - $elms = []; - $buffer = ""; + $elms = []; + $buffer = ""; $clearBuffer = function() use (&$elms, &$buffer) { array_push($elms, $this->document->createTextNode($buffer)); @@ -164,7 +170,8 @@ class Parser { TokenType::BACKTICK, TokenType::ASTERISK, TokenType::LBRACKET, - TokenType::BANG + TokenType::BANG, + TokenType::LBRACE ])) { $this->consume()->data; // backslash $buffer .= $this->consume()->data; @@ -228,6 +235,41 @@ class Parser { $clearBuffer(); array_push($elms, $elm); continue; + } elseif ($this->current()->type === TokenType::LBRACE) { + $lbrace = $this->consume(); + assert($lbrace->type === TokenType::LBRACE, "expected left brace, got ".$lbrace->type->name); + + $content = ""; + while ($this->current()->type !== TokenType::EOF && + $this->current()->type !== TokenType::EOL && + $this->current()->type !== TokenType::RBRACE) { + $content .= $this->consume()->data; + } + $rbrace = $this->consume(); + assert($rbrace->type === TokenType::RBRACE, "expected right brace, got ".$rbrace->type->name); + + $attributes = array_map(function($element) { + return trim($element); + }, explode(',', $content)); + + $obj = new Attributes(); + + foreach($attributes as $attribute) { + if (!in_array($attribute[0], [".", "#"])) + continue; + switch ($attribute[0]) { + case ".": + array_push($obj->classes, substr($attribute, 1)); + break; + case "#": + $obj->id = substr($attribute, 1); + break; + default: + continue 2; + } + } + + array_push($elms, $obj); } else $buffer .= self::StripBackslashes($this->consume()->data); } @@ -268,6 +310,7 @@ class Parser { // then we parse the node content $elm = $this->document->createElement("li"); foreach ($this->parseText() as $node) + if ($node instanceof DOMNode) $elm->appendChild($node); // now we check, if the level of the next line is higher than the current level. @@ -303,10 +346,17 @@ class Parser { private function buildParagraph(array $elms) : void { if (count($elms) < 1) return; - $elm = $this->document->createElement("p"); $i = 0; foreach ($elms as $node) { + if ($node instanceof Attributes) { + if (count($node->classes) > 0) + $elm->setAttribute("class", join(" ", $node->classes)); + if ($node->id) + $elm->setAttribute("id", $node->id); + + continue; + } if ($node->nodeName === "#text" && trim($node->textContent) === "") continue; $elm->appendChild($node); @@ -326,7 +376,8 @@ class Parser { } $elm = $this->document->createElement("h".$level); foreach ($this->parseText() as $node) - $elm->appendChild($node); + if ($node instanceof DOMNode) + $elm->appendChild($node); $this->document->appendChild($elm); } diff --git a/src/Token.php b/src/Token.php index d3a4b78..a0cf6dc 100644 --- a/src/Token.php +++ b/src/Token.php @@ -22,6 +22,8 @@ enum TokenType { case PIPE ; case GT ; case TAB ; + case LBRACE ; + case RBRACE ; } class Token { diff --git a/tests/AnnotationsTest.php b/tests/AnnotationsTest.php new file mode 100644 index 0000000..953b15b --- /dev/null +++ b/tests/AnnotationsTest.php @@ -0,0 +1,70 @@ +This is an H1 +
+ this is a code block
+
+ ";
+
+ [$source, $result] = createTest($source, $target);
+ $this->assertEquals($source, $result);
+ }
+}
\ No newline at end of file
diff --git a/tests/GenericBlocksTest.php b/tests/GenericBlocksTest.php
index 43cd151..57e673a 100644
--- a/tests/GenericBlocksTest.php
+++ b/tests/GenericBlocksTest.php
@@ -44,4 +44,20 @@ this is a paragraph
[$source, $result] = createTest($source, $target);
$this->assertEquals($source, $result);
}
+
+ public function testParagraphAnnotations() : void {
+ $source = "
+
+this is an annotated paragraph {.classA, .classB}
+
+ ";
+ $target = "
+ + this is an annotated paragraph +
+ "; + + [$source, $result] = createTest($source, $target); + $this->assertEquals($source, $result); + } } \ No newline at end of file