Skip to content

Commit

Permalink
Allow nested selectors (#12)
Browse files Browse the repository at this point in the history
This PR allows for nested selectors. You can for example only target p-tags inside a list:

 ```php
 'li p' => 'my-css-class', 
```

This wasn't possible before.
  • Loading branch information
Jonas Siewertsen authored Aug 19, 2021
1 parent 242481f commit 0a1a634
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 47 deletions.
53 changes: 21 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,49 +5,39 @@

## Usage

There might be cases, if for example using [TailwindCss](https://tailwindcss.com), where you want to use custom classes to style your output.

### Getting started
```php
use VV\Markdown\Facades\Markdown;

Markdown::parse($yourContent); // The outpul will be HTML
```

### Output styling

#### Basic
You can always format your markdown by using a wrapper of some kind.
```css
.markdown h1 {
font-weight: bold;
...
}
### Configuration
To add or change style sets, simply add or change an array with classes that should be added to the HTML tag.
```php
'default' => [
'h1' => 'text-2xl',
'a' => 'link hover:text-blue',
'p' => 'mb-5',
'li p' => 'mb-2 ml-4',
],
```
*This example uses TailwindCSS, but you can use whatever kind of CSS you want.*

### Example Output
```html
<div class="markdown">
<!-- Auto generated output -->
<h1>Headline</h1>
<p>Some text</p>
</div>
```
<h1 class="text-2xl">A headline</h1>
<p class="mb-5">Some text</p>

### Custom classes
There might be cases, if for example using [TailwindCss](https://tailwindcss.com), where you want to use custom classes to style your output.
<ul>
<li><p class="mb-2 ml-4">A list item</p></li>
<li><p class="mb-2 ml-4">A list item</p></li>
<li><p class="mb-2 ml-4"><a class="link hover:text-blue" href="#">Klick me</a></p></li>
</ul>

```php
// config/markdown.php
'styles' => [
'default' => [
'h1' => 'text-2xl',
'p' => 'mb-2',
],
...
```

The output would look like this
```html
<h1 class="text-2xl">Headline</h1>
<p class="mb-2">Some text</p>
<p class="mb-5">Another text</p>
```

#### Multiple styles
Expand Down Expand Up @@ -75,7 +65,6 @@ Markdown::style('wiki')->parse($yourContent);
No need to define default. If nothing has been provied, markdown will look for the default style.



# More about us
- [www.visuellverstehen.de](https://visuellverstehen.de)

Expand Down
56 changes: 47 additions & 9 deletions src/Markdown/AddCustomHtmlClasses.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,19 @@

class AddCustomHtmlClasses
{
/**
* The content the class will parse.
*/
public string $content;

/**
* The chosen style which normaly would be `default`.
*/
public string $style;

/**
* The belonging style stet as defined inside the config file.
*/
public array $styleSet;

public function __construct(string $content, string $style)
Expand All @@ -21,29 +32,56 @@ public function __construct(string $content, string $style)
*/
public function handle(): string
{
foreach ($this->styleSet as $tag => $class) {
$this->content = str_replace($this->tagFilter($tag), $this->replaceTag($tag, $class), $this->content);
}
// Fetching all style sets and ordering them after the count.
$tags = collect($this->getStyleSet())
->map(fn ($classes, $tags) => new Tag($tags, $classes))
->sortByDesc('count');

// Parse the content for every defined style set.
$tags->each(function ($tag) {
$this->content = $this->parse($tag, $this->content);
});

return $this->content;
}

/**
* Build the string we want to replace.
* Is parsing the content and will add classes to the html-tags.
*/
private function tagFilter(string $tag): string
public function parse(Tag $tag, string $value): string
{
return "<{$tag}";
return preg_replace(
$this->defineRegexPattern($tag),
$this->defineReplacement($tag),
$value
);
}

/*
* Defines the regex pattern and does take nested selectors into account.
*/
private function defineRegexPattern(Tag $tag): string
{
$pattern = '';

foreach ($tag->before as $name) {
$pattern .= "<{$name}[^>]*>[^<]*";
}

return "/({$pattern})(<{$tag->tag})(?! class)/iU";
}

/**
* Replace the filtered tag and add custom css classes.
* Does add the needed classes into the html tag.
*/
private function replaceTag(string $tag, string $class): string
private function defineReplacement(Tag $tag): string
{
return "<{$tag} class=\"{$class}\"";
return "$1<{$tag->tag} class=\"{$tag->classes}\"";
}

/**
* Get the style sets from the config for the chosen style set.
*/
private function getStyleSet(): array
{
$configPath = 'markdown.styles.'.$this->style;
Expand Down
7 changes: 3 additions & 4 deletions src/Markdown/CommonMarkRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
class CommonMarkRepository implements MarkdownRepository
{
public GithubFlavoredMarkdownConverter $parser;

public string $style = 'default';

public function __construct(array $config)
Expand All @@ -17,11 +18,9 @@ public function __construct(array $config)
public function parse(string $content): string
{
$content = $this->parser->convertToHtml($content);
$content = (new PrefixImageSources($content))->handle();

return (new PrefixImageSources(
(new AddCustomHtmlClasses($content, $this->style))->handle()
)
)->handle();
return (new AddCustomHtmlClasses($content, $this->style))->handle();
}

public function style(string $style): self
Expand Down
61 changes: 61 additions & 0 deletions src/Markdown/Tag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

namespace VV\Markdown\Markdown;

class Tag
{
/*
* Contains the parent tag, which might be the only or last tag defined in the config.
* From a list like `ul li p` it would fx be `p`.
*/
public string $tag;

/*
* If multiple tags have been defined, those are the tags before the parent tag.
* From a list like `ul li p` it would fx be `ul li`.
*/
public array $before;

/*
* Contains the class list as defined in the config.
*/
public string $classes;

/*
* Defines the number of tags, including the parent tag.
*/
public int $count;

public function __construct(string $tags, string $classes)
{
$tags = $this->convertTagsToArray($tags);
$this->count = count($tags);
$this->classes = $classes;
$this->setTags($tags);
}

/*
* It's easier to handle tags as an array, so we'll convert them.
*/
private function convertTagsToArray(string $tags): array
{
return explode(' ', $tags);
}

/*
* Set the parent tag and save all other tags in the before attribute.
*/
private function setTags(array $tags): void
{
$this->tag = $this->setParentTagAndRemoveFromOriginalArray($tags);
$this->before = $tags;
}

/*
* Get the key from the last array and splice / remove it.
*/
private function setParentTagAndRemoveFromOriginalArray(array &$tags)
{
return array_splice($tags, array_key_last($tags))[0];
}
}
89 changes: 87 additions & 2 deletions tests/Unit/CommonMarkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,94 @@ public function it_can_parse_lists()
<li>Second item</li>
<li>Third item</li>
<li>Fourth item</li>
</ul>';
</ul>
';

$this->assertStringcontainsString($result, Markdown::parse($toParse));
$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
public function a_nested_tag_will_be_recognized()
{
config()->set('markdown.styles.default', ['li p' => 'text-sm']);

$toParse = '<li><p>Some text</p></li>';
$result = '<li><p class="text-sm">Some text</p></li>
';

$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
public function a_nested_tag_with_text_inbetween_will_be_recognized()
{
config()->set('markdown.styles.default', ['a span' => 'text-red']);

$toParse = '<a>Some <span>styled</span> text</a>';
$result = '<p><a>Some <span class="text-red">styled</span> text</a></p>
';

$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
public function a_nested_tag_with_text_inbetween_will_be_recognized_on_multilines_as_well()
{
config()->set('markdown.styles.default', ['li p' => 'text-bold']);

$toParse = <<<'EOT'
<li>Bad formatted HTML
<p>Some more</p>
</li>
EOT;

$result = <<<'EOT'
<li>Bad formatted HTML
<p class="text-bold">Some more</p>
</li>

EOT;

$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
public function a_nested_tag_with_already_defined_classes_will_be_parsed_correctly()
{
config()->set('markdown.styles.default', ['a span' => 'text-red']);

$toParse = '<a href="#">Some<span>thing</span></a>';
$result = '<p><a href="#">Some<span class="text-red">thing</span></a></p>
';

$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
public function a_nested_tag_will_be_replaced_and_wont_be_overwritten()
{
config()->set('markdown.styles.default', [
'p' => 'single',
'li p' => 'nested',
]);

$toParse = <<<'EOT'
<li>
<p>I am nested</p>
</li>
<p>I am not</p>
EOT;

$result = <<<'EOT'
<li>
<p class="nested">I am nested</p>
</li>
<p class="single">I am not</p>

EOT;

$this->assertEquals($result, Markdown::parse($toParse));
}

/** @test */
Expand Down

0 comments on commit 0a1a634

Please sign in to comment.