Merge pull request 'Paragraph annotations' (#2) from feature/paragraph-annotations into development

Reviewed-on: MassiveDynamic/Parkdown#2
master
Michael Ochmann 3 years ago
commit e6ba398b14
  1. 16
      README.md
  2. 8
      src/Lexer.php
  3. 61
      src/Parser.php
  4. 2
      src/Token.php
  5. 70
      tests/AnnotationsTest.php
  6. 16
      tests/GenericBlocksTest.php

@ -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
<p class="thisIsAClass anotherClass" id="thisIsAnID">
Paragraphs can be annotated with ids and classes
</p>
```
### Images
```markdown
![this is an alt text](https://images.unsplash.com/photo-1571171637578-41bc2dd41cd2?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&h=300&w=1740&q=80\)

@ -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;

@ -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);
}

@ -22,6 +22,8 @@ enum TokenType {
case PIPE ;
case GT ;
case TAB ;
case LBRACE ;
case RBRACE ;
}
class Token {

@ -0,0 +1,70 @@
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class AnnotationsTest extends TestCase {
public function testAnnotationsDoNotBreakHeadings() : void {
$source = "
# This is an H1 {.withAClass}
## This is an H2 {.withAClass}
### This is an H3 {.withAClass}
#### This is an H4 {.withAClass}
##### This is an H5 {.withAClass}
";
$target = "
<h1>This is an H1</h1>
<h2>This is an H2</h2>
<h3>This is an H3</h3>
<h4>This is an H4</h4>
<h5>This is an H5</h5>
";
[$source, $result] = createTest($source, $target);
$this->assertEquals($source, $result);
}
public function testAnnotationsDoNotBreakLists() : void {
$source = "
* this is a list
* with annotations {#someID}
1. this is an ordered list
1. with an annotation {.someClass}
";
$target = "
<ul>
<li>this is a list</li>
<li>with annotations</li>
</ul>
<ol>
<li>
this is an ordered list
<ol>
<li>with an annotation</li>
</ol>
</li>
</ol>
";
[$source, $result] = createTest($source, $target);
$this->assertEquals($source, $result);
}
public function testAnnotationsDoNotBreakCodeblocks() : void {
$source = "
```
this is a code block
``` {.someClass}
";
$target = "
<pre>
<code>this is a code block</code>
</pre>
";
[$source, $result] = createTest($source, $target);
$this->assertEquals($source, $result);
}
}

@ -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 = "
<p class=\"classA classB\">
this is an annotated paragraph
</p>
";
[$source, $result] = createTest($source, $target);
$this->assertEquals($source, $result);
}
}
Loading…
Cancel
Save