mirror of
https://github.com/erusev/parsedown.git
synced 2023-08-10 21:13:06 +03:00
Compare commits
1 Commits
v2.0.0-bet
...
test/sheph
Author | SHA1 | Date | |
---|---|---|---|
488ecc0377 |
98
.github/workflows/ci.yml
vendored
98
.github/workflows/ci.yml
vendored
@ -1,98 +0,0 @@
|
||||
name: Parsedown
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
units:
|
||||
name: Unit Tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1, 8.0, 7.4, 7.3, 7.2, 7.1]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run Tests
|
||||
run: |
|
||||
composer remove infection/infection --no-update --dev
|
||||
composer remove roave/infection-static-analysis-plugin --no-update --dev
|
||||
composer update --prefer-dist --no-interaction --no-progress
|
||||
composer test-units
|
||||
|
||||
mutations:
|
||||
name: Mutation Tests
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run Tests
|
||||
run: |
|
||||
composer update --prefer-dist --no-interaction --no-progress
|
||||
vendor/bin/roave-infection-static-analysis-plugin --show-mutations --threads=4 --min-msi=80 --min-covered-msi=80
|
||||
|
||||
static-analysis:
|
||||
name: Code Format and Static Analysis
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run Tests
|
||||
run: |
|
||||
composer install --prefer-dist --no-interaction --no-progress
|
||||
composer test-static -- --shepherd
|
||||
composer test-formatting
|
||||
composer test-dead-code
|
||||
|
||||
commonmark:
|
||||
name: CommonMark
|
||||
continue-on-error: true
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
php: [8.1]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Set up PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
tools: composer:v2
|
||||
|
||||
- uses: actions/checkout@v2
|
||||
- name: Install
|
||||
run: composer install --prefer-dist --no-interaction --no-progress
|
||||
|
||||
- name: CommonMark Strict
|
||||
continue-on-error: true
|
||||
run: composer test-commonmark
|
||||
|
||||
- name: CommonMark Weak
|
||||
continue-on-error: true
|
||||
run: composer test-commonmark-weak
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -2,5 +2,3 @@ composer.lock
|
||||
vendor/
|
||||
infection.log
|
||||
tests/spec_cache.txt
|
||||
.phpunit.result.cache
|
||||
composer.lock
|
||||
|
@ -29,9 +29,9 @@ $rules = [
|
||||
'strict_param' => true,
|
||||
'whitespace_after_comma_in_array' => true,
|
||||
];
|
||||
return (new Config)
|
||||
return Config::create()
|
||||
->setRules($rules)
|
||||
->setFinder($finder)
|
||||
->setUsingCache(false)
|
||||
->setRiskyAllowed(true)
|
||||
;
|
||||
;
|
73
.travis.yml
Normal file
73
.travis.yml
Normal file
@ -0,0 +1,73 @@
|
||||
language: php
|
||||
|
||||
dist: trusty
|
||||
sudo: false
|
||||
|
||||
stages:
|
||||
- Code Format and Static Analysis
|
||||
- Units
|
||||
- Test CommonMark (weak)
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
allow_failures:
|
||||
- env: ALLOW_FAILURE
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.composer/cache
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Code Format and Static Analysis
|
||||
php: 7.3
|
||||
install: composer install --prefer-dist --no-interaction --no-progress
|
||||
script:
|
||||
- '[ -z "$TRAVIS_TAG" ] || [ "$TRAVIS_TAG" == "$(php -r "require(\"vendor/autoload.php\"); echo Erusev\Parsedown\Parsedown::version;")" ]'
|
||||
- composer test-static -- --shepherd
|
||||
- composer test-formatting
|
||||
- composer test-dead-code
|
||||
|
||||
|
||||
- &UNIT_TEST
|
||||
stage: Units
|
||||
php: 5.5
|
||||
install:
|
||||
# remove packages with PHP requirements higher than 7.0 to prevent composer trying to resolve these, see: https://github.com/composer/composer/issues/6011
|
||||
- composer remove vimeo/psalm friendsofphp/php-cs-fixer infection/infection --dev --no-update --no-interaction
|
||||
- composer install --prefer-dist --no-interaction --no-progress
|
||||
script: composer test-units
|
||||
- <<: *UNIT_TEST
|
||||
php: 5.6
|
||||
- <<: *UNIT_TEST
|
||||
php: 7.0
|
||||
- &MUTATION_AND_UNIT_TEST
|
||||
<<: *UNIT_TEST
|
||||
php: 7.1
|
||||
install:
|
||||
- composer install --prefer-dist --no-interaction --no-progress
|
||||
script:
|
||||
- composer test-units
|
||||
- vendor/bin/infection --show-mutations --threads=4 --min-msi=90 --min-covered-msi=90
|
||||
- <<: *MUTATION_AND_UNIT_TEST
|
||||
php: 7.2
|
||||
- <<: *MUTATION_AND_UNIT_TEST
|
||||
php: 7.3
|
||||
- <<: *UNIT_TEST
|
||||
php: nightly
|
||||
env: ALLOW_FAILURE
|
||||
|
||||
|
||||
- &COMMONMARK_TEST
|
||||
stage: CommonMark
|
||||
name: Weak
|
||||
php: 7.3
|
||||
env: ALLOW_FAILURE
|
||||
install: composer install --prefer-dist --no-interaction --no-progress
|
||||
script:
|
||||
- composer test-commonmark-weak
|
||||
|
||||
- <<: *COMMONMARK_TEST
|
||||
name: Strict
|
||||
script:
|
||||
- composer test-commonmark
|
@ -13,15 +13,14 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.1||^8.0",
|
||||
"php": "^7||^5.5",
|
||||
"ext-mbstring": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^9.3.11||^8.5.21||^7.5.20",
|
||||
"vimeo/psalm": "^4.10.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.0.0",
|
||||
"infection/infection": "^0.25.0",
|
||||
"roave/infection-static-analysis-plugin": "^1.10.0"
|
||||
"phpunit/phpunit": "^7.4||^6.5.13||^5.7.27||^4.8.36",
|
||||
"vimeo/psalm": "^3.2.7",
|
||||
"friendsofphp/php-cs-fixer": "^2.13",
|
||||
"infection/infection": "^0.12.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {"Erusev\\Parsedown\\": "src/"}
|
||||
|
@ -1,222 +0,0 @@
|
||||
# Implementing "Extensions" in v2.0
|
||||
|
||||
Parsedown v1.x allowed extensability through class extensions, where an developer
|
||||
could extend the core Parsedown class, and access or override any of the `protected`
|
||||
level methods and variables.
|
||||
|
||||
Whilst this approach allows huge breadth to the type of functionality that can
|
||||
be added by an extension, it has some downsides too:
|
||||
|
||||
* ### Composability: extensions cannot be combined easily
|
||||
An extension must extend another extension for two extensions to work together.
|
||||
This limits the usefulness of small extensions, because they cannot be combined with another small or popular extension.
|
||||
If an extension author wishes the extension to be compatible with another extension, they can only pick one.
|
||||
|
||||
* ### API stability
|
||||
Because extensions have access to functions and variables at the `protected` API layer, it is hard to determine impacts of
|
||||
internal changes. Yet, without being able to make a certain amount of internal change it is impractical to fix bugs or develop
|
||||
new features. In the `1.x` branch, `1.8` was never released outside of a "beta" version for this reason: changes in the
|
||||
`protected` API layer would break extensions.
|
||||
|
||||
In order to address these concerns, "extensions" in Parsedown v2.0 will become more like "plugins", and with that comes a lot of
|
||||
flexability.
|
||||
|
||||
ParsedownExtra is a popular extension for Parsedown, and this has been completely re-implemented for 2.0. In order to use
|
||||
ParsedownExtra with Parsedown, a user simply needs to write the following:
|
||||
|
||||
```php
|
||||
$Parsedown = new Parsedown(new ParsedownExtra);
|
||||
$actualMarkup = $Parsedown->toHtml($markdown);
|
||||
```
|
||||
|
||||
Here, ParsedownExtra is *composed* with Parsedown, but does not extend it.
|
||||
|
||||
A key feature of *composability* is the ability to compose *multiple* extensions together, for example another
|
||||
extension, say, `ParsedownMath` could be composed with `ParsedownExtra` in a user-defined order.
|
||||
|
||||
This time using the `::from` method, rather than the convinence constructor provided by `ParsedownExtra`.
|
||||
|
||||
```php
|
||||
$Parsedown = new Parsedown(ParsedownExtra::from(ParsedownMath::from(new State)));
|
||||
```
|
||||
|
||||
```php
|
||||
$Parsedown = new Parsedown(ParsedownMath::from(ParsedownExtra::from(new State)));
|
||||
```
|
||||
|
||||
In the above, the first object that we initialise the chain of composed extensions is the `State` object. This `State`
|
||||
object is passed from `ParsedownExtra` to `ParsedownMath`, and then finally, to `Parsedown`. At each stage new
|
||||
information is added to the `State`: adding or removing parsing instructions, and to enabling or disabling features.
|
||||
|
||||
The `State` object both contains instructions for how to parse a document (e.g. new blocks and inlines), as well as
|
||||
information used throughout parsing (such as link reference definitions, or recursion depth). By writing `new State`,
|
||||
we create a `State` object that is setup with Parsedown's default behaviours, and by passing that object through
|
||||
different extensions (using the `::from` method), these extensions are free to alter, add to, or remove from that
|
||||
default behaviour.
|
||||
|
||||
## Introduction to the `State` Object
|
||||
Key to Parsedown's new composability for extensions is the `State` object.
|
||||
|
||||
This name is a little obtuse, but is importantly accurate.
|
||||
|
||||
A `State` object incorporates `Block`s, `Inline`s, some additional render steps, and any custom configuration options that
|
||||
the user might want to set. This can **fully** control how a document is parsed and rendered.
|
||||
|
||||
In the above code, `ParsedownExtra` and `ParsedownMath` would both be implementing the `StateBearer` interface, which
|
||||
essentially means "this class holds onto a particular Parsedown State". A `StateBearer` should be constructable from
|
||||
an existing `State` via `::from(StateBearer $StateBearer)`, and reveals the `State` it is holding onto via `->state(): State`.
|
||||
|
||||
Implementing the `StateBearer` interface is **strongly encouraged** if implementing an extension, but not necessarily required.
|
||||
In the end, you can modify Parsedown's behaviour by producing an appropriate `State` object (which itself is trivially a
|
||||
`StateBearer`).
|
||||
|
||||
In general, extensions are encouraged to go further still, and split each self-contained piece of functionality out into its own
|
||||
`StateBearer`. This will allow your users to cherry-pick specific pieces of functionality and combine it with other
|
||||
functionality from different authors as they like. For example, a feature of ParsedownExtra is the ability to define and expand
|
||||
"abbreviations". This feature is self-contained, and does not depend on other features (e.g. "footnotes").
|
||||
|
||||
A user could import *only* the abbreviations feature from ParsedownExtra by using the following:
|
||||
|
||||
```php
|
||||
use Erusev\Parsedown\State;
|
||||
use Erusev\Parsedown\Parsedown;
|
||||
use Erusev\ParsedownExtra\Features\Abbreviations;
|
||||
|
||||
$State = Abbreviations::from(new State);
|
||||
|
||||
$Parsedown = new Parsedown($State);
|
||||
$actualMarkup = $Parsedown->toHtml($markdown);
|
||||
```
|
||||
|
||||
This allows a user to have fine-grained control over which features they import, and will allow them much more control over
|
||||
combining features from multiple sources. E.g. a user may not like the way ParsedownExtra has implemented the "footnotes" feature,
|
||||
and so may wish to utilise an implementation from another source. By implementing each feature as its own `StateBearer`, we give
|
||||
users the freedom to compose features in a way that works for them.
|
||||
|
||||
## Anatomy of the `State` Object
|
||||
|
||||
The `State` object, generically, consists of a set of `Configurable`s. The word "set" is important here: only one instance of each
|
||||
`Configurable` may exist in a `State`. If you need to store related data in a `Configurable`, your `Configurable` needs to handle
|
||||
this containerisation itself.
|
||||
|
||||
`State` has a special property: all `Configurable`s "exist" in any `State` object when retrieving that `Configurable` with `->get`.
|
||||
|
||||
This means that retrieval cannot fail when using this method, though does mean that all `Configurable`s need to be "default constructable" (i.e. can be constructed into a "default" state). All `Configurable`s must therefore implement the static method
|
||||
`initial`, which must return an instance of the given `Configurable`. No initial data will be provided, but the `Configurable` **must** arrive at some sane default instance.
|
||||
|
||||
`Configurable`s must also be immutable, unless they declare themeslves otherwise by implementing the `MutableConfigurable` interface.
|
||||
|
||||
### Blocks
|
||||
One of the "core" `Configurable`s in Parsedown is `BlockTypes`. This contains a mapping of "markers" (a character that Parsedown
|
||||
looks for, before handing off to the block-specific parser), and a list of `Block`s that can begin parsing from a specific marker.
|
||||
Also contained, is a list of "unmarked" blocks, which Parsedown will hand off to prior to trying any marked blocks. Within marked
|
||||
blocks there is also a precedence order, where the first block type to successfully parse in this list will be the one chosen.
|
||||
|
||||
The default value given by `BlockTypes::initial()` consists of Parsedown's default blocks. The following is a snapshot of this list:
|
||||
|
||||
```php
|
||||
const DEFAULT_BLOCK_TYPES = [
|
||||
'#' => [Header::class],
|
||||
'*' => [Rule::class, TList::class],
|
||||
'+' => [TList::class],
|
||||
'-' => [SetextHeader::class, Table::class, Rule::class, TList::class],
|
||||
...
|
||||
```
|
||||
|
||||
This means that if a `-` marker is found, Parsedown will first try to parse a `SetextHeader`, then try to parse a `Table`, and
|
||||
so on...
|
||||
|
||||
A new block can be added to this list in several ways. ParsedownExtra, for example, adds a new `Abbreviation` block as follows:
|
||||
|
||||
```php
|
||||
$BlockTypes = $State->get(BlockTypes::class)
|
||||
->addingMarkedLowPrecedence('*', [Abbreviation::class])
|
||||
;
|
||||
|
||||
$State = $State->setting($BlockTypes);
|
||||
```
|
||||
|
||||
This first retrieves the current value of the `BlockTypes` configurable, adds `Abbreviation` with low precedence (i.e. the
|
||||
back of the list) to the `*` marker, and then updates the `$State` object by using the `->setting` method.
|
||||
|
||||
### Immutability
|
||||
|
||||
Note that the `->setting` method must be used to create a new instance of the `State` object because `BlockTypes` is immutable,
|
||||
the same will be true of most configurables. This approach is preferred because mutations to `State` are localised by default: i.e.
|
||||
only affect copies of `$State` which we provide to other methods, but does not affect copies of `$State` which were provided to our
|
||||
code by a parent caller.
|
||||
|
||||
Localised mutability allows for more sensible reasoning by default, for example (this time talking about `Inline`s), the `Link` inline
|
||||
can enforce that no inline `Url`s are parsed (which would cause double links in output when parsing something like:
|
||||
`[https://example.com](https://example.com)`). This can be done by updating the copy of `$State` which is passed down to lower level
|
||||
parsers to simply no longer include parsing of `Url`s:
|
||||
|
||||
```php
|
||||
$State = $State->setting(
|
||||
$State->get(InlineTypes::class)->removing([Url::class])
|
||||
);
|
||||
```
|
||||
|
||||
If `InlineTypes` were mutable, this change would not only affect decendent parsing, but would also affect all parsing which occured after our link was parsed (i.e. would stop URL parsing from that point on in the document).
|
||||
|
||||
Another use case for this is implementing a recursion limiter (which *is* implemented as a configurable). After a user-specifiable
|
||||
max-depth is exceeded: further parsing will halt. The implementaion for this is extremely simple, only because of immutability.
|
||||
|
||||
### Mutability
|
||||
The preference toward immutability by default is not an assertion that "mutability is bad", rather that "unexpected mutability
|
||||
is bad". By opting-in to mutability, we can treat mutability with the care it deserves.
|
||||
|
||||
While immutabiltiy can do a lot to simplify reasoning in the majority of cases, there are some cirumstances where mutability is
|
||||
required to implement a specific feature. An exmaple of this is found in ParsedownExtra's "abbreviations" feature, which implements
|
||||
the following:
|
||||
|
||||
```php
|
||||
final class AbbreviationBook implements MutableConfigurable
|
||||
{
|
||||
/** @var array<string, string> */
|
||||
private $book;
|
||||
|
||||
/**
|
||||
* @param array<string, string> $book
|
||||
*/
|
||||
public function __construct(array $book = [])
|
||||
{
|
||||
$this->book = $book;
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function initial()
|
||||
{
|
||||
return new self;
|
||||
}
|
||||
|
||||
public function mutatingSet(string $abbreviation, string $definition): void
|
||||
{
|
||||
$this->book[$abbreviation] = $definition;
|
||||
}
|
||||
|
||||
public function lookup(string $abbreviation): ?string
|
||||
{
|
||||
return $this->book[$abbreviation] ?? null;
|
||||
}
|
||||
|
||||
/** @return array<string, string> */
|
||||
public function all()
|
||||
{
|
||||
return $this->book;
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public function isolatedCopy(): self
|
||||
{
|
||||
return new self($this->book);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Under the hood, `AbbreviationBook` is nothing more than a string-to-string mapping between an abbreviation, and its definition.
|
||||
|
||||
The powerful feature here is that when an abbreviation is identified during parsing, that definition can be updated immediately
|
||||
everywhere, without needing to worry about the current parsing depth, or organise an alternate method to sharing this data. Footnotes
|
||||
also make use of this with a `FootnoteBook`, with slightly more complexity in what is stored (so that inline references can be
|
||||
individually numbered).
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
errorLevel="1"
|
||||
totallyTyped="true"
|
||||
strictBinaryOperands="true"
|
||||
checkForThrowsDocblock="true"
|
||||
>
|
||||
@ -19,7 +19,7 @@
|
||||
<errorLevel type="suppress"><directory name="tests" /></errorLevel>
|
||||
</PropertyNotSetInConstructor>
|
||||
<UnusedClass>
|
||||
<errorLevel type="suppress"><directory name="tests" /></errorLevel>
|
||||
<errorLevel type="suppress"><directory name="tests/src" /></errorLevel>
|
||||
</UnusedClass>
|
||||
<UndefinedInterfaceMethod>
|
||||
<errorLevel type="suppress"><directory name="tests/src" /></errorLevel>
|
||||
@ -30,10 +30,5 @@
|
||||
<PossiblyNullReference>
|
||||
<errorLevel type="suppress"><directory name="tests/src" /></errorLevel>
|
||||
</PossiblyNullReference>
|
||||
<InternalMethod>
|
||||
<errorLevel type="suppress">
|
||||
<directory name="tests" />
|
||||
</errorLevel>
|
||||
</InternalMethod>
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -61,7 +62,7 @@ final class BlockQuote implements ContinuableBlock
|
||||
*/
|
||||
public function advance(Context $Context, State $State)
|
||||
{
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -81,7 +82,7 @@ final class BlockQuote implements ContinuableBlock
|
||||
return new self($Lines);
|
||||
}
|
||||
|
||||
if (!($Context->precedingEmptyLines() > 0)) {
|
||||
if (! $Context->previousEmptyLines() > 0) {
|
||||
$indentOffset = $Context->line()->indentOffset() + $Context->line()->indent();
|
||||
$Lines = $this->Lines->appendingTextLines($Context->line()->text(), $indentOffset);
|
||||
|
||||
@ -92,7 +93,7 @@ final class BlockQuote implements ContinuableBlock
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{Block[], State}
|
||||
* @return array{0: Block[], 1: State}
|
||||
*/
|
||||
public function contents(State $State)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -87,7 +88,7 @@ final class FencedCode implements ContinuableBlock
|
||||
|
||||
$newCode = $this->code;
|
||||
|
||||
$newCode .= $Context->precedingEmptyLinesText();
|
||||
$newCode .= $Context->previousEmptyLinesText();
|
||||
|
||||
if (($len = \strspn($Context->line()->text(), $this->marker)) >= $this->openerLength
|
||||
&& \chop(\substr($Context->line()->text(), $len), ' ') === ''
|
||||
|
@ -3,9 +3,8 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Configurables\HeaderSlug;
|
||||
use Erusev\Parsedown\Configurables\SlugRegister;
|
||||
use Erusev\Parsedown\Configurables\StrictMode;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Parsedown;
|
||||
@ -97,17 +96,9 @@ final class Header implements Block
|
||||
return new Handler(
|
||||
/** @return Element */
|
||||
function (State $State) {
|
||||
$HeaderSlug = $State->get(HeaderSlug::class);
|
||||
$Register = $State->get(SlugRegister::class);
|
||||
$attributes = (
|
||||
$HeaderSlug->isEnabled()
|
||||
? ['id' => $HeaderSlug->transform($Register, $this->text())]
|
||||
: []
|
||||
);
|
||||
|
||||
return new Element(
|
||||
'h' . \strval($this->level()),
|
||||
$attributes,
|
||||
[],
|
||||
$State->applyTo(Parsedown::line($this->text(), $State))
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -34,7 +35,7 @@ final class IndentedCode implements ContinuableBlock
|
||||
State $State,
|
||||
Block $Block = null
|
||||
) {
|
||||
if (isset($Block) && $Block instanceof Paragraph && ! ($Context->precedingEmptyLines() > 0)) {
|
||||
if (isset($Block) && $Block instanceof Paragraph && ! $Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -60,8 +61,8 @@ final class IndentedCode implements ContinuableBlock
|
||||
|
||||
$offset = $Context->line()->indentOffset();
|
||||
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
foreach (\explode("\n", $Context->precedingEmptyLinesText()) as $line) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
foreach (\explode("\n", $Context->previousEmptyLinesText()) as $line) {
|
||||
$newCode .= (new Line($line, $offset))->ltrimBodyUpto(4) . "\n";
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Configurables\SafeMode;
|
||||
@ -14,81 +15,18 @@ use Erusev\Parsedown\State;
|
||||
|
||||
final class Markup implements ContinuableBlock
|
||||
{
|
||||
private const REGEX_HTML_ATTRIBUTE = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
|
||||
const REGEX_HTML_ATTRIBUTE = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
|
||||
|
||||
private const BLOCK_ELEMENTS = [
|
||||
'address' => true,
|
||||
'article' => true,
|
||||
'aside' => true,
|
||||
'base' => true,
|
||||
'basefont' => true,
|
||||
'blockquote' => true,
|
||||
'body' => true,
|
||||
'caption' => true,
|
||||
'center' => true,
|
||||
'col' => true,
|
||||
'colgroup' => true,
|
||||
'dd' => true,
|
||||
'details' => true,
|
||||
'dialog' => true,
|
||||
'dir' => true,
|
||||
'div' => true,
|
||||
'dl' => true,
|
||||
'dt' => true,
|
||||
'fieldset' => true,
|
||||
'figcaption' => true,
|
||||
'figure' => true,
|
||||
'footer' => true,
|
||||
'form' => true,
|
||||
'frame' => true,
|
||||
'frameset' => true,
|
||||
'h1' => true,
|
||||
'h2' => true,
|
||||
'h3' => true,
|
||||
'h4' => true,
|
||||
'h5' => true,
|
||||
'h6' => true,
|
||||
'head' => true,
|
||||
'header' => true,
|
||||
'hr' => true,
|
||||
'html' => true,
|
||||
'iframe' => true,
|
||||
'legend' => true,
|
||||
'li' => true,
|
||||
'link' => true,
|
||||
'main' => true,
|
||||
'menu' => true,
|
||||
'menuitem' => true,
|
||||
'nav' => true,
|
||||
'noframes' => true,
|
||||
'ol' => true,
|
||||
'optgroup' => true,
|
||||
'option' => true,
|
||||
'p' => true,
|
||||
'param' => true,
|
||||
'section' => true,
|
||||
'source' => true,
|
||||
'summary' => true,
|
||||
'table' => true,
|
||||
'tbody' => true,
|
||||
'td' => true,
|
||||
'tfoot' => true,
|
||||
'th' => true,
|
||||
'thead' => true,
|
||||
'title' => true,
|
||||
'tr' => true,
|
||||
'track' => true,
|
||||
'ul' => true,
|
||||
];
|
||||
|
||||
private const SIMPLE_CONTAINS_END_CONDITIONS = [
|
||||
/** @var array{2: string, 3: string, 4: string, 5: string} */
|
||||
private static $simpleContainsEndConditions = [
|
||||
2 => '-->',
|
||||
3 => '?>',
|
||||
4 => '>',
|
||||
5 => ']]>',
|
||||
5 => ']]>'
|
||||
];
|
||||
|
||||
private const SPECIAL_HTML_BLOCK_TAGS = [
|
||||
/** @var array<string, string> */
|
||||
private static $specialHtmlBlockTags = [
|
||||
'script' => true,
|
||||
'style' => true,
|
||||
'pre' => true,
|
||||
@ -149,37 +87,17 @@ final class Markup implements ContinuableBlock
|
||||
return new self($rawLine, 5, self::closes12345TypeMarkup(5, $text));
|
||||
}
|
||||
|
||||
if (\preg_match('/^<([\/]?+)(\w++)(.*+)$/', $text, $matches)) {
|
||||
$isClosing = ($matches[1] === '/');
|
||||
$element = \strtolower($matches[2]);
|
||||
$tail = $matches[3];
|
||||
|
||||
if (\array_key_exists($element, self::BLOCK_ELEMENTS)
|
||||
&& \preg_match('/^(?:\s|$|>|\/)/', $tail)
|
||||
) {
|
||||
return new self($rawLine, 6);
|
||||
}
|
||||
if (\preg_match('/^<[\/]?+(\w++)(?:[ ]*+'.self::REGEX_HTML_ATTRIBUTE.')*+[ ]*+(\/)?>/', $text, $matches)) {
|
||||
$element = \strtolower($matches[1]);
|
||||
|
||||
if (
|
||||
! $isClosing && \preg_match(
|
||||
'/^(?:[ ]*+'.self::REGEX_HTML_ATTRIBUTE.')*(?:[ ]*+)[\/]?+[>](.*+)$/',
|
||||
$tail,
|
||||
$matches
|
||||
) || $isClosing && \preg_match(
|
||||
'/^(?:[ ]*+)[\/]?+[>](.*+)$/',
|
||||
$tail,
|
||||
$matches
|
||||
)
|
||||
\array_key_exists($element, Element::$TEXT_LEVEL_ELEMENTS)
|
||||
|| \array_key_exists($element, self::$specialHtmlBlockTags)
|
||||
) {
|
||||
$tail = $matches[1];
|
||||
|
||||
if (! \array_key_exists($element, self::SPECIAL_HTML_BLOCK_TAGS)
|
||||
&& ! (isset($Block) && $Block instanceof Paragraph && $Context->precedingEmptyLines() < 1)
|
||||
&& \preg_match('/^\s*+$/', $tail)
|
||||
) {
|
||||
return new self($rawLine, 7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return new self($rawLine, 6);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -199,7 +117,7 @@ final class Markup implements ContinuableBlock
|
||||
return null;
|
||||
}
|
||||
|
||||
if (($type === 6 || $type === 7) && $Context->precedingEmptyLines() > 0) {
|
||||
if (($type === 6 || $type === 7) && $Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -207,7 +125,7 @@ final class Markup implements ContinuableBlock
|
||||
$closed = self::closes12345TypeMarkup($type, $Context->line()->text());
|
||||
}
|
||||
|
||||
$html = $this->html . \str_repeat("\n", $Context->precedingEmptyLines() + 1);
|
||||
$html = $this->html . \str_repeat("\n", $Context->previousEmptyLines() + 1);
|
||||
$html .= $Context->line()->rawLine();
|
||||
|
||||
return new self($html, $type, $closed);
|
||||
@ -224,7 +142,7 @@ final class Markup implements ContinuableBlock
|
||||
if (\preg_match('/<\/(?:script|pre|style)>/i', $text)) {
|
||||
return true;
|
||||
}
|
||||
} elseif (\stripos($text, self::SIMPLE_CONTAINS_END_CONDITIONS[$type]) !== false) {
|
||||
} elseif (\stripos($text, self::$simpleContainsEndConditions[$type]) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -44,7 +45,7 @@ final class Paragraph implements ContinuableBlock
|
||||
*/
|
||||
public function advance(Context $Context, State $State)
|
||||
{
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\StateUpdatingBlock;
|
||||
use Erusev\Parsedown\Configurables\DefinitionBook;
|
||||
@ -42,7 +43,9 @@ final class Reference implements StateUpdatingBlock
|
||||
'title' => isset($matches[3]) ? $matches[3] : null,
|
||||
];
|
||||
|
||||
$State->get(DefinitionBook::class)->mutatingSet($id, $Data);
|
||||
$State = $State->setting(
|
||||
$State->get(DefinitionBook::class)->setting($id, $Data)
|
||||
);
|
||||
|
||||
return new self($State);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Parsing\Context;
|
||||
|
@ -3,10 +3,9 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\AcquisitioningBlock;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Configurables\HeaderSlug;
|
||||
use Erusev\Parsedown\Configurables\SlugRegister;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Parsedown;
|
||||
use Erusev\Parsedown\Parsing\Context;
|
||||
@ -41,7 +40,7 @@ final class SetextHeader implements AcquisitioningBlock
|
||||
State $State,
|
||||
Block $Block = null
|
||||
) {
|
||||
if (! isset($Block) || ! $Block instanceof Paragraph || $Context->precedingEmptyLines() > 0) {
|
||||
if (! isset($Block) || ! $Block instanceof Paragraph || $Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -89,17 +88,9 @@ final class SetextHeader implements AcquisitioningBlock
|
||||
return new Handler(
|
||||
/** @return Element */
|
||||
function (State $State) {
|
||||
$HeaderSlug = $State->get(HeaderSlug::class);
|
||||
$Register = $State->get(SlugRegister::class);
|
||||
$attributes = (
|
||||
$HeaderSlug->isEnabled()
|
||||
? ['id' => $HeaderSlug->transform($Register, $this->text())]
|
||||
: []
|
||||
);
|
||||
|
||||
return new Element(
|
||||
'h' . \strval($this->level()),
|
||||
$attributes,
|
||||
[],
|
||||
$State->applyTo(Parsedown::line($this->text(), $State))
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -133,7 +134,7 @@ final class TList implements ContinuableBlock
|
||||
$listStart !== 1
|
||||
&& isset($Block)
|
||||
&& $Block instanceof Paragraph
|
||||
&& ! ($Context->precedingEmptyLines() > 0)
|
||||
&& ! $Context->previousEmptyLines() > 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
@ -160,11 +161,11 @@ final class TList implements ContinuableBlock
|
||||
*/
|
||||
public function advance(Context $Context, State $State)
|
||||
{
|
||||
if ($Context->precedingEmptyLines() > 0 && \end($this->Lis)->isEmpty()) {
|
||||
if ($Context->previousEmptyLines() > 0 && \end($this->Lis)->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$newlines = \str_repeat("\n", $Context->precedingEmptyLines());
|
||||
$newlines = \str_repeat("\n", $Context->previousEmptyLines());
|
||||
|
||||
$requiredIndent = $this->indent + \strlen($this->marker) + $this->afterMarkerSpaces;
|
||||
$isLoose = $this->isLoose;
|
||||
@ -172,16 +173,16 @@ final class TList implements ContinuableBlock
|
||||
|
||||
$Lis = $this->Lis;
|
||||
|
||||
if ($this->type === 'ol') {
|
||||
$regex = '/^([0-9]++'.$this->markerTypeRegex.')([\t ]++.*|$)/';
|
||||
} else {
|
||||
$regex = '/^('.$this->markerTypeRegex.')([\t ]++.*|$)/';
|
||||
}
|
||||
|
||||
if ($Context->line()->indent() < $requiredIndent
|
||||
&& \preg_match($regex, $Context->line()->text(), $matches)
|
||||
&& ((
|
||||
$this->type === 'ol'
|
||||
&& \preg_match('/^([0-9]++'.$this->markerTypeRegex.')([\t ]++.*|$)/', $Context->line()->text(), $matches)
|
||||
) || (
|
||||
$this->type === 'ul'
|
||||
&& \preg_match('/^('.$this->markerTypeRegex.')([\t ]++.*|$)/', $Context->line()->text(), $matches)
|
||||
))
|
||||
) {
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingBlankLines(1);
|
||||
|
||||
$isLoose = true;
|
||||
@ -223,8 +224,8 @@ final class TList implements ContinuableBlock
|
||||
}
|
||||
|
||||
if ($Context->line()->indent() >= $requiredIndent) {
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingBlankLines($Context->precedingEmptyLines());
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingBlankLines($Context->previousEmptyLines());
|
||||
|
||||
$isLoose = true;
|
||||
}
|
||||
@ -247,7 +248,7 @@ final class TList implements ContinuableBlock
|
||||
);
|
||||
}
|
||||
|
||||
if (! ($Context->precedingEmptyLines() > 0)) {
|
||||
if (! $Context->previousEmptyLines() > 0) {
|
||||
$text = $Context->line()->ltrimBodyUpto($requiredIndent);
|
||||
|
||||
$Lis[\count($Lis) -1] = $Lis[\count($Lis) -1]->appendingTextLines(
|
||||
@ -272,12 +273,12 @@ final class TList implements ContinuableBlock
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{Block[], State}[]
|
||||
* @return array{0: Block[], 1: State}[]
|
||||
*/
|
||||
public function items(State $State)
|
||||
{
|
||||
return \array_map(
|
||||
/** @return array{Block[], State} */
|
||||
/** @return array{0: Block[], 1: State} */
|
||||
function (Lines $Lines) use ($State) {
|
||||
return Parsedown::blocks($Lines, $State);
|
||||
},
|
||||
@ -316,7 +317,7 @@ final class TList implements ContinuableBlock
|
||||
),
|
||||
\array_map(
|
||||
/**
|
||||
* @param array{Block[], State} $Item
|
||||
* @param array{0: Block[], 1: State} $Item
|
||||
* @return Element
|
||||
* */
|
||||
function ($Item) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Blocks;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\AcquisitioningBlock;
|
||||
use Erusev\Parsedown\Components\Block;
|
||||
use Erusev\Parsedown\Components\ContinuableBlock;
|
||||
@ -17,25 +18,30 @@ use Erusev\Parsedown\State;
|
||||
*/
|
||||
final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
{
|
||||
/** @var list<_Alignment|null> */
|
||||
/** @var bool */
|
||||
private $acquired;
|
||||
|
||||
/** @var array<int, _Alignment|null> */
|
||||
private $alignments;
|
||||
|
||||
/** @var list<string> */
|
||||
/** @var array<int, string> */
|
||||
private $headerCells;
|
||||
|
||||
/** @var list<list<string>> */
|
||||
/** @var array<int, array<int, string>> */
|
||||
private $rows;
|
||||
|
||||
/**
|
||||
* @param list<_Alignment|null> $alignments
|
||||
* @param list<string> $headerCells
|
||||
* @param list<list<string>> $rows
|
||||
* @param array<int, _Alignment|null> $alignments
|
||||
* @param array<int, string> $headerCells
|
||||
* @param array<int, array<int, string>> $rows
|
||||
* @param bool $acquired
|
||||
*/
|
||||
private function __construct($alignments, $headerCells, $rows)
|
||||
private function __construct($alignments, $headerCells, $rows, $acquired = false)
|
||||
{
|
||||
$this->alignments = $alignments;
|
||||
$this->headerCells = $headerCells;
|
||||
$this->rows = $rows;
|
||||
$this->acquired = $acquired;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -85,7 +91,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
|
||||
# ~
|
||||
|
||||
return new self($alignments, $headerCells, []);
|
||||
return new self($alignments, $headerCells, [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -95,7 +101,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
*/
|
||||
public function advance(Context $Context, State $State)
|
||||
{
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -111,14 +117,12 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
if (
|
||||
! \preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches)
|
||||
|| ! isset($matches[0])
|
||||
|| ! \is_array($matches[0])
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cells = \array_map(
|
||||
'trim',
|
||||
\array_slice($matches[0], 0, \count($this->alignments))
|
||||
);
|
||||
$cells = \array_map('trim', \array_slice($matches[0], 0, \count($this->alignments)));
|
||||
|
||||
return new self(
|
||||
$this->alignments,
|
||||
@ -129,7 +133,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
|
||||
/**
|
||||
* @param string $dividerRow
|
||||
* @return list<_Alignment|null>|null
|
||||
* @return array<int, _Alignment|null>|null
|
||||
*/
|
||||
private static function parseAlignments($dividerRow)
|
||||
{
|
||||
@ -138,7 +142,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
|
||||
$dividerCells = \explode('|', $dividerRow);
|
||||
|
||||
/** @var list<_Alignment|null> */
|
||||
/** @var array<int, _Alignment|null> */
|
||||
$alignments = [];
|
||||
|
||||
foreach ($dividerCells as $dividerCell) {
|
||||
@ -171,7 +175,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @return list<Inline[]> */
|
||||
/** @return array<int, Inline[]> */
|
||||
public function headerRow(State $State)
|
||||
{
|
||||
return \array_map(
|
||||
@ -186,13 +190,13 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
);
|
||||
}
|
||||
|
||||
/** @return list<Inline[]>[] */
|
||||
/** @return array<int, Inline[]>[] */
|
||||
public function rows(State $State)
|
||||
{
|
||||
return \array_map(
|
||||
/**
|
||||
* @param list<string> $cells
|
||||
* @return list<Inline[]>
|
||||
* @param array<int, string> $cells
|
||||
* @return array<int, Inline[]>
|
||||
*/
|
||||
function ($cells) use ($State) {
|
||||
return \array_map(
|
||||
@ -210,7 +214,7 @@ final class Table implements AcquisitioningBlock, ContinuableBlock
|
||||
);
|
||||
}
|
||||
|
||||
/** @return list<_Alignment|null> */
|
||||
/** @return array<int, _Alignment|null> */
|
||||
public function alignments()
|
||||
{
|
||||
return $this->alignments;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
@ -20,12 +21,14 @@ final class Emphasis implements Inline
|
||||
/** @var 'em'|'strong' */
|
||||
private $type;
|
||||
|
||||
private const STRONG_REGEX = [
|
||||
/** @var array{*: string, _: string} */
|
||||
private static $STRONG_REGEX = [
|
||||
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
|
||||
'_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
|
||||
];
|
||||
|
||||
private const EM_REGEX = [
|
||||
/** @var array{*: string, _: string} */
|
||||
private static $EM_REGEX = [
|
||||
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
|
||||
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
||||
];
|
||||
@ -55,9 +58,9 @@ final class Emphasis implements Inline
|
||||
return null;
|
||||
}
|
||||
|
||||
if (\preg_match(self::STRONG_REGEX[$marker], $Excerpt->text(), $matches)) {
|
||||
if (\preg_match(self::$STRONG_REGEX[$marker], $Excerpt->text(), $matches)) {
|
||||
$emphasis = 'strong';
|
||||
} elseif (\preg_match(self::EM_REGEX[$marker], $Excerpt->text(), $matches)) {
|
||||
} elseif (\preg_match(self::$EM_REGEX[$marker], $Excerpt->text(), $matches)) {
|
||||
$emphasis = 'em';
|
||||
} else {
|
||||
return null;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
use Erusev\Parsedown\Parsing\Excerpt;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\BacktrackingInline;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Configurables\SafeMode;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -70,12 +71,12 @@ final class Image implements Inline
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Handler<Element>
|
||||
* @return Handler<Element|Text>
|
||||
*/
|
||||
public function stateRenderable()
|
||||
{
|
||||
return new Handler(
|
||||
/** @return Element */
|
||||
/** @return Element|Text */
|
||||
function (State $State) {
|
||||
$attributes = [
|
||||
'src' => $this->url(),
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Configurables\DefinitionBook;
|
||||
use Erusev\Parsedown\Configurables\InlineTypes;
|
||||
@ -112,12 +113,12 @@ final class Link implements Inline
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Handler<Element>
|
||||
* @return Handler<Element|Text>
|
||||
*/
|
||||
public function stateRenderable()
|
||||
{
|
||||
return new Handler(
|
||||
/** @return Element */
|
||||
/** @return Element|Text */
|
||||
function (State $State) {
|
||||
$attributes = ['href' => $this->url()];
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Configurables\SafeMode;
|
||||
use Erusev\Parsedown\Html\Renderables\RawHtml;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
use Erusev\Parsedown\Parsing\Excerpt;
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\BacktrackingInline;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Configurables\Breaks;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\RawHtml;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\Handler;
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\BacktrackingInline;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
@ -13,9 +14,6 @@ final class Url implements BacktrackingInline
|
||||
{
|
||||
use WidthTrait;
|
||||
|
||||
private const URI = 'https?+:[^\s[:cntrl:]<>]*';
|
||||
private const NO_TRAILING_PUNCT = '(?<![?!.,:*_~])';
|
||||
|
||||
/** @var string */
|
||||
private $url;
|
||||
|
||||
@ -40,31 +38,13 @@ final class Url implements BacktrackingInline
|
||||
*/
|
||||
public static function build(Excerpt $Excerpt, State $State)
|
||||
{
|
||||
// this needs some work to follow spec
|
||||
if (
|
||||
\preg_match(
|
||||
'/'.self::URI.self::NO_TRAILING_PUNCT.'/iu',
|
||||
$Excerpt->context(),
|
||||
$matches,
|
||||
\PREG_OFFSET_CAPTURE
|
||||
)
|
||||
) {
|
||||
/** @var array{0: array{string, int}} $matches */
|
||||
$url = $matches[0][0];
|
||||
$position = \intval($matches[0][1]);
|
||||
|
||||
if (\preg_match('/[)]++$/', $url, $matches)) {
|
||||
$trailingParens = \strlen($matches[0]);
|
||||
|
||||
$openingParens = \substr_count($url, '(');
|
||||
$closingParens = \substr_count($url, ')');
|
||||
|
||||
if ($closingParens > $openingParens) {
|
||||
$url = \substr($url, 0, -\min($trailingParens, $closingParens - $openingParens));
|
||||
}
|
||||
}
|
||||
|
||||
return new self($url, $position);
|
||||
if (\preg_match(
|
||||
'/(?<=^|\s|[*_~(])https?+:[\/]{2}[^\s<]+\b\/*+/ui',
|
||||
$Excerpt->context(),
|
||||
$matches,
|
||||
\PREG_OFFSET_CAPTURE
|
||||
)) {
|
||||
return new self($matches[0][0], \intval($matches[0][1]));
|
||||
}
|
||||
|
||||
return null;
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace Erusev\Parsedown\Components\Inlines;
|
||||
|
||||
use Erusev\Parsedown\AST\StateRenderable;
|
||||
use Erusev\Parsedown\Components\Inline;
|
||||
use Erusev\Parsedown\Html\Renderables\Element;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
|
@ -17,7 +17,8 @@ use Erusev\Parsedown\Configurable;
|
||||
|
||||
final class BlockTypes implements Configurable
|
||||
{
|
||||
private const DEFAULT_BLOCK_TYPES = [
|
||||
/** @var array<array-key, array<int, class-string<Block>>> */
|
||||
private static $defaultBlockTypes = [
|
||||
'#' => [Header::class],
|
||||
'*' => [Rule::class, TList::class],
|
||||
'+' => [TList::class],
|
||||
@ -43,19 +44,20 @@ final class BlockTypes implements Configurable
|
||||
'~' => [FencedCode::class],
|
||||
];
|
||||
|
||||
private const DEFAULT_UNMARKED_BLOCK_TYPES = [
|
||||
/** @var array<int, class-string<Block>> */
|
||||
private static $defaultUnmarkedBlockTypes = [
|
||||
IndentedCode::class,
|
||||
];
|
||||
|
||||
/** @var array<array-key, list<class-string<Block>>> */
|
||||
/** @var array<array-key, array<int, class-string<Block>>> */
|
||||
private $blockTypes;
|
||||
|
||||
/** @var list<class-string<Block>> */
|
||||
/** @var array<int, class-string<Block>> */
|
||||
private $unmarkedBlockTypes;
|
||||
|
||||
/**
|
||||
* @param array<array-key, list<class-string<Block>>> $blockTypes
|
||||
* @param list<class-string<Block>> $unmarkedBlockTypes
|
||||
* @param array<array-key, array<int, class-string<Block>>> $blockTypes
|
||||
* @param array<int, class-string<Block>> $unmarkedBlockTypes
|
||||
*/
|
||||
public function __construct(array $blockTypes, array $unmarkedBlockTypes)
|
||||
{
|
||||
@ -67,14 +69,14 @@ final class BlockTypes implements Configurable
|
||||
public static function initial()
|
||||
{
|
||||
return new self(
|
||||
self::DEFAULT_BLOCK_TYPES,
|
||||
self::DEFAULT_UNMARKED_BLOCK_TYPES
|
||||
self::$defaultBlockTypes,
|
||||
self::$defaultUnmarkedBlockTypes
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Block>> $newBlockTypes
|
||||
* @param array<int, class-string<Block>> $newBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function settingMarked($marker, array $newBlockTypes)
|
||||
@ -87,7 +89,7 @@ final class BlockTypes implements Configurable
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Block>> $newBlockTypes
|
||||
* @param array<int, class-string<Block>> $newBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingMarkedHighPrecedence($marker, array $newBlockTypes)
|
||||
@ -101,48 +103,9 @@ final class BlockTypes implements Configurable
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Block> $searchBlockType
|
||||
* @param class-string<Block> $replacementBlockType
|
||||
*/
|
||||
public function replacing($searchBlockType, $replacementBlockType): self
|
||||
{
|
||||
$replacer = self::makeReplacer($searchBlockType, $replacementBlockType);
|
||||
|
||||
return new self(
|
||||
\array_map($replacer, $this->blockTypes),
|
||||
$replacer($this->unmarkedBlockTypes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Block> $searchBlockType
|
||||
* @param class-string<Block> $replacementBlockType
|
||||
* @return \Closure(list<class-string<Block>>):list<class-string<Block>>
|
||||
*/
|
||||
private static function makeReplacer($searchBlockType, $replacementBlockType)
|
||||
{
|
||||
/**
|
||||
* @param list<class-string<Block>> $blockTypes
|
||||
* @return list<class-string<Block>>
|
||||
*/
|
||||
return function ($blockTypes) use ($searchBlockType, $replacementBlockType) {
|
||||
return \array_map(
|
||||
/**
|
||||
* @param class-string<Block> $blockType
|
||||
* @return class-string<Block>
|
||||
*/
|
||||
function ($blockType) use ($searchBlockType, $replacementBlockType) {
|
||||
return $blockType === $searchBlockType ? $replacementBlockType : $blockType;
|
||||
},
|
||||
$blockTypes
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Block>> $newBlockTypes
|
||||
* @param array<int, class-string<Block>> $newBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingMarkedLowPrecedence($marker, array $newBlockTypes)
|
||||
@ -157,7 +120,7 @@ final class BlockTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string<Block>> $newUnmarkedBlockTypes
|
||||
* @param array<int, class-string<Block>> $newUnmarkedBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function settingUnmarked(array $newUnmarkedBlockTypes)
|
||||
@ -166,7 +129,7 @@ final class BlockTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string<Block>> $newBlockTypes
|
||||
* @param array<int, class-string<Block>> $newBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingUnmarkedHighPrecedence(array $newBlockTypes)
|
||||
@ -177,7 +140,7 @@ final class BlockTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string<Block>> $newBlockTypes
|
||||
* @param array<int, class-string<Block>> $newBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingUnmarkedLowPrecedence(array $newBlockTypes)
|
||||
@ -188,7 +151,7 @@ final class BlockTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string<Block>> $removeBlockTypes
|
||||
* @param array<int, class-string<Block>> $removeBlockTypes
|
||||
* @return self
|
||||
*/
|
||||
public function removing(array $removeBlockTypes)
|
||||
@ -196,21 +159,21 @@ final class BlockTypes implements Configurable
|
||||
return new self(
|
||||
\array_map(
|
||||
/**
|
||||
* @param list<class-string<Block>> $blockTypes
|
||||
* @return list<class-string<Block>>
|
||||
* @param array<int, class-string<Block>> $blockTypes
|
||||
* @return array<int, class-string<Block>>
|
||||
*/
|
||||
function ($blockTypes) use ($removeBlockTypes) {
|
||||
return \array_values(\array_diff($blockTypes, $removeBlockTypes));
|
||||
return \array_diff($blockTypes, $removeBlockTypes);
|
||||
},
|
||||
$this->blockTypes
|
||||
),
|
||||
\array_values(\array_diff($this->unmarkedBlockTypes, $removeBlockTypes))
|
||||
\array_diff($this->unmarkedBlockTypes, $removeBlockTypes)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @return list<class-string<Block>>
|
||||
* @return array<int, class-string<Block>>
|
||||
*/
|
||||
public function markedBy($marker)
|
||||
{
|
||||
@ -222,7 +185,7 @@ final class BlockTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<class-string<Block>>
|
||||
* @return array<int, class-string<Block>>
|
||||
*/
|
||||
public function unmarked()
|
||||
{
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
namespace Erusev\Parsedown\Configurables;
|
||||
|
||||
use Erusev\Parsedown\MutableConfigurable;
|
||||
use Erusev\Parsedown\Configurable;
|
||||
|
||||
/**
|
||||
* @psalm-type _Data=array{url: string, title: string|null}
|
||||
*/
|
||||
final class DefinitionBook implements MutableConfigurable
|
||||
final class DefinitionBook implements Configurable
|
||||
{
|
||||
/** @var array<string, _Data> */
|
||||
private $book;
|
||||
@ -29,10 +29,14 @@ final class DefinitionBook implements MutableConfigurable
|
||||
/**
|
||||
* @param string $id
|
||||
* @param _Data $data
|
||||
* @return self
|
||||
*/
|
||||
public function mutatingSet($id, array $data): void
|
||||
public function setting($id, array $data)
|
||||
{
|
||||
$this->book[$id] = $data;
|
||||
$book = $this->book;
|
||||
$book[$id] = $data;
|
||||
|
||||
return new self($book);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -47,10 +51,4 @@ final class DefinitionBook implements MutableConfigurable
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function isolatedCopy(): self
|
||||
{
|
||||
return new self($this->book);
|
||||
}
|
||||
}
|
||||
|
@ -1,100 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Configurables;
|
||||
|
||||
use Erusev\Parsedown\Configurable;
|
||||
|
||||
final class HeaderSlug implements Configurable
|
||||
{
|
||||
/** @var bool */
|
||||
private $enabled = false;
|
||||
|
||||
/** @var \Closure(string):string */
|
||||
private $slugCallback;
|
||||
|
||||
/** @var \Closure(string,int):string */
|
||||
private $duplicationCallback;
|
||||
|
||||
/**
|
||||
* @param bool $enabled
|
||||
* @param (\Closure(string):string)|null $slugCallback
|
||||
* @param (\Closure(string, int):string)|null $duplicationCallback
|
||||
*/
|
||||
public function __construct(
|
||||
$enabled,
|
||||
$slugCallback = null,
|
||||
$duplicationCallback = null
|
||||
) {
|
||||
$this->enabled = $enabled;
|
||||
|
||||
if (! isset($slugCallback)) {
|
||||
$this->slugCallback = function (string $text): string {
|
||||
$slug = \mb_strtolower($text);
|
||||
$slug = \str_replace(' ', '-', $slug);
|
||||
$slug = \preg_replace('/[^\p{L}\p{Nd}\p{Nl}\p{M}-]+/u', '', $slug);
|
||||
$slug = \trim($slug, '-');
|
||||
|
||||
return $slug;
|
||||
};
|
||||
} else {
|
||||
$this->slugCallback = $slugCallback;
|
||||
}
|
||||
|
||||
if (! isset($duplicationCallback)) {
|
||||
$this->duplicationCallback = function (string $slug, int $duplicateNumber): string {
|
||||
return $slug . '-' . \strval($duplicateNumber-1);
|
||||
};
|
||||
} else {
|
||||
$this->duplicationCallback = $duplicationCallback;
|
||||
}
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEnabled()
|
||||
{
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
public function transform(SlugRegister $SlugRegister, string $text): string
|
||||
{
|
||||
$slug = ($this->slugCallback)($text);
|
||||
|
||||
if ($SlugRegister->slugCount($slug) > 0) {
|
||||
$newSlug = ($this->duplicationCallback)($slug, $SlugRegister->mutatingIncrement($slug));
|
||||
|
||||
while ($SlugRegister->slugCount($newSlug) > 0) {
|
||||
$newSlug = ($this->duplicationCallback)($slug, $SlugRegister->mutatingIncrement($slug));
|
||||
}
|
||||
|
||||
return $newSlug;
|
||||
}
|
||||
|
||||
$SlugRegister->mutatingIncrement($slug);
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/** @param \Closure(string):string $slugCallback */
|
||||
public static function withCallback($slugCallback): self
|
||||
{
|
||||
return new self(true, $slugCallback);
|
||||
}
|
||||
|
||||
/** @param \Closure(string,int):string $duplicationCallback */
|
||||
public static function withDuplicationCallback($duplicationCallback): self
|
||||
{
|
||||
return new self(true, null, $duplicationCallback);
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function enabled()
|
||||
{
|
||||
return new self(true);
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function initial()
|
||||
{
|
||||
return new self(false);
|
||||
}
|
||||
}
|
@ -20,7 +20,8 @@ use Erusev\Parsedown\Configurable;
|
||||
|
||||
final class InlineTypes implements Configurable
|
||||
{
|
||||
private const DEFAULT_INLINE_TYPES = [
|
||||
/** @var array<array-key, array<int, class-string<Inline>>> */
|
||||
private static $defaultInlineTypes = [
|
||||
'!' => [Image::class],
|
||||
'*' => [Emphasis::class],
|
||||
'_' => [Emphasis::class],
|
||||
@ -34,14 +35,14 @@ final class InlineTypes implements Configurable
|
||||
"\n" => [HardBreak::class, SoftBreak::class],
|
||||
];
|
||||
|
||||
/** @var array<array-key, list<class-string<Inline>>> */
|
||||
/** @var array<array-key, array<int, class-string<Inline>>> */
|
||||
private $inlineTypes;
|
||||
|
||||
/** @var string */
|
||||
private $inlineMarkers;
|
||||
|
||||
/**
|
||||
* @param array<array-key, list<class-string<Inline>>> $inlineTypes
|
||||
* @param array<array-key, array<int, class-string<Inline>>> $inlineTypes
|
||||
*/
|
||||
public function __construct(array $inlineTypes)
|
||||
{
|
||||
@ -52,12 +53,12 @@ final class InlineTypes implements Configurable
|
||||
/** @return self */
|
||||
public static function initial()
|
||||
{
|
||||
return new self(self::DEFAULT_INLINE_TYPES);
|
||||
return new self(self::$defaultInlineTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Inline>> $newInlineTypes
|
||||
* @param array<int, class-string<Inline>> $newInlineTypes
|
||||
* @return self
|
||||
*/
|
||||
public function setting($marker, array $newInlineTypes)
|
||||
@ -68,38 +69,9 @@ final class InlineTypes implements Configurable
|
||||
return new self($inlineTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<Inline> $searchInlineType
|
||||
* @param class-string<Inline> $replacementInlineType
|
||||
*/
|
||||
public function replacing($searchInlineType, $replacementInlineType): self
|
||||
{
|
||||
return new self(
|
||||
\array_map(
|
||||
/**
|
||||
* @param list<class-string<Inline>> $inlineTypes
|
||||
* @return list<class-string<Inline>>
|
||||
*/
|
||||
function ($inlineTypes) use ($searchInlineType, $replacementInlineType) {
|
||||
return \array_map(
|
||||
/**
|
||||
* @param class-string<Inline> $inlineType
|
||||
* @return class-string<Inline>
|
||||
*/
|
||||
function ($inlineType) use ($searchInlineType, $replacementInlineType) {
|
||||
return $inlineType === $searchInlineType ? $replacementInlineType : $inlineType;
|
||||
},
|
||||
$inlineTypes
|
||||
);
|
||||
},
|
||||
$this->inlineTypes
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Inline>> $newInlineTypes
|
||||
* @param array<int, class-string<Inline>> $newInlineTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingHighPrecedence($marker, array $newInlineTypes)
|
||||
@ -115,7 +87,7 @@ final class InlineTypes implements Configurable
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @param list<class-string<Inline>> $newInlineTypes
|
||||
* @param array<int, class-string<Inline>> $newInlineTypes
|
||||
* @return self
|
||||
*/
|
||||
public function addingLowPrecedence($marker, array $newInlineTypes)
|
||||
@ -130,18 +102,18 @@ final class InlineTypes implements Configurable
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<class-string<Inline>> $removeInlineTypes
|
||||
* @param array<int, class-string<Inline>> $removeInlineTypes
|
||||
* @return self
|
||||
*/
|
||||
public function removing(array $removeInlineTypes)
|
||||
{
|
||||
return new self(\array_map(
|
||||
/**
|
||||
* @param list<class-string<Inline>> $inlineTypes
|
||||
* @return list<class-string<Inline>>
|
||||
* @param array<int, class-string<Inline>> $inlineTypes
|
||||
* @return array<int, class-string<Inline>>
|
||||
*/
|
||||
function ($inlineTypes) use ($removeInlineTypes) {
|
||||
return \array_values(\array_diff($inlineTypes, $removeInlineTypes));
|
||||
return \array_diff($inlineTypes, $removeInlineTypes);
|
||||
},
|
||||
$this->inlineTypes
|
||||
));
|
||||
@ -149,7 +121,7 @@ final class InlineTypes implements Configurable
|
||||
|
||||
/**
|
||||
* @param string $marker
|
||||
* @return list<class-string<Inline>>
|
||||
* @return array<int, class-string<Inline>>
|
||||
*/
|
||||
public function markedBy($marker)
|
||||
{
|
||||
|
@ -38,7 +38,7 @@ final class RecursionLimiter implements Configurable
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public function incremented()
|
||||
public function increment()
|
||||
{
|
||||
return new self($this->maxDepth, $this->currentDepth + 1);
|
||||
}
|
||||
|
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Configurables;
|
||||
|
||||
use Erusev\Parsedown\Configurable;
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\State;
|
||||
|
||||
final class RenderStack implements Configurable
|
||||
{
|
||||
/** @var list<\Closure(Renderable[],State):Renderable[]> */
|
||||
private $stack;
|
||||
|
||||
/**
|
||||
* @param list<\Closure(Renderable[],State):Renderable[]> $stack
|
||||
*/
|
||||
private function __construct($stack = [])
|
||||
{
|
||||
$this->stack = $stack;
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function initial()
|
||||
{
|
||||
return new self;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(Renderable[],State):Renderable[] $RenderMap
|
||||
* @return self
|
||||
*/
|
||||
public function push(\Closure $RenderMap): self
|
||||
{
|
||||
return new self(\array_merge($this->stack, [$RenderMap]));
|
||||
}
|
||||
|
||||
/** @return list<\Closure(Renderable[],State):Renderable[]> */
|
||||
public function getStack(): array
|
||||
{
|
||||
return $this->stack;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Configurables;
|
||||
|
||||
use Erusev\Parsedown\MutableConfigurable;
|
||||
|
||||
final class SlugRegister implements MutableConfigurable
|
||||
{
|
||||
/** @var array<string, int> */
|
||||
private $register;
|
||||
|
||||
/**
|
||||
* @param array<string, int> $register
|
||||
*/
|
||||
public function __construct(array $register = [])
|
||||
{
|
||||
$this->register = $register;
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function initial()
|
||||
{
|
||||
return new self;
|
||||
}
|
||||
|
||||
public function mutatingIncrement(string $slug): int
|
||||
{
|
||||
if (! isset($this->register[$slug])) {
|
||||
$this->register[$slug] = 0;
|
||||
}
|
||||
|
||||
return ++$this->register[$slug];
|
||||
}
|
||||
|
||||
public function slugCount(string $slug): int
|
||||
{
|
||||
return $this->register[$slug] ?? 0;
|
||||
}
|
||||
|
||||
/** @return static */
|
||||
public function isolatedCopy(): self
|
||||
{
|
||||
return new self($this->register);
|
||||
}
|
||||
}
|
@ -3,9 +3,8 @@
|
||||
namespace Erusev\Parsedown\Html\Renderables;
|
||||
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\Html\TransformableRenderable;
|
||||
|
||||
final class Container implements TransformableRenderable
|
||||
final class Container implements Renderable
|
||||
{
|
||||
use CanonicalStateRenderable;
|
||||
|
||||
@ -15,7 +14,7 @@ final class Container implements TransformableRenderable
|
||||
/**
|
||||
* @param Renderable[] $Contents
|
||||
*/
|
||||
public function __construct($Contents = [])
|
||||
public function __construct($Contents)
|
||||
{
|
||||
$this->Contents = $Contents;
|
||||
}
|
||||
@ -28,10 +27,6 @@ final class Container implements TransformableRenderable
|
||||
return $this->Contents;
|
||||
}
|
||||
|
||||
public function adding(Renderable $Renderable): Container
|
||||
{
|
||||
return new Container(\array_merge($this->Contents, [$Renderable]));
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function getHtml()
|
||||
@ -49,36 +44,4 @@ final class Container implements TransformableRenderable
|
||||
''
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(string):TransformableRenderable $Transform
|
||||
* @return TransformableRenderable
|
||||
*/
|
||||
public function transformingContent(\Closure $Transform): TransformableRenderable
|
||||
{
|
||||
return new Container(\array_map(
|
||||
function (Renderable $R) use ($Transform): Renderable {
|
||||
if (! $R instanceof TransformableRenderable) {
|
||||
return $R;
|
||||
}
|
||||
|
||||
return $R->transformingContent($Transform);
|
||||
},
|
||||
$this->Contents
|
||||
));
|
||||
}
|
||||
|
||||
public function replacingAll(string $search, TransformableRenderable $Replacement): TransformableRenderable
|
||||
{
|
||||
return new Container(\array_map(
|
||||
function (Renderable $R) use ($search, $Replacement): Renderable {
|
||||
if (! $R instanceof TransformableRenderable) {
|
||||
return $R;
|
||||
}
|
||||
|
||||
return $R->replacingAll($search, $Replacement);
|
||||
},
|
||||
$this->Contents
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,13 @@ namespace Erusev\Parsedown\Html\Renderables;
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\Html\Sanitisation\CharacterFilter;
|
||||
use Erusev\Parsedown\Html\Sanitisation\Escaper;
|
||||
use Erusev\Parsedown\Html\TransformableRenderable;
|
||||
|
||||
final class Element implements TransformableRenderable
|
||||
final class Element implements Renderable
|
||||
{
|
||||
use CanonicalStateRenderable;
|
||||
|
||||
const TEXT_LEVEL_ELEMENTS = [
|
||||
/** @var array<string, true> */
|
||||
public static $TEXT_LEVEL_ELEMENTS = [
|
||||
'a' => true,
|
||||
'b' => true,
|
||||
'i' => true,
|
||||
@ -167,7 +167,7 @@ final class Element implements TransformableRenderable
|
||||
foreach ($this->Contents as $C) {
|
||||
if (
|
||||
$C instanceof Element
|
||||
&& ! \array_key_exists(\strtolower($C->name()), self::TEXT_LEVEL_ELEMENTS)
|
||||
&& ! \array_key_exists(\strtolower($C->name()), self::$TEXT_LEVEL_ELEMENTS)
|
||||
) {
|
||||
$html .= "\n";
|
||||
}
|
||||
@ -179,7 +179,7 @@ final class Element implements TransformableRenderable
|
||||
|
||||
if (
|
||||
$Last instanceof Element
|
||||
&& ! \array_key_exists(\strtolower($Last->name()), self::TEXT_LEVEL_ELEMENTS)
|
||||
&& ! \array_key_exists(\strtolower($Last->name()), self::$TEXT_LEVEL_ELEMENTS)
|
||||
) {
|
||||
$html .= "\n";
|
||||
}
|
||||
@ -192,44 +192,4 @@ final class Element implements TransformableRenderable
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(string):TransformableRenderable $Transform
|
||||
* @return TransformableRenderable
|
||||
*/
|
||||
public function transformingContent(\Closure $Transform): TransformableRenderable
|
||||
{
|
||||
if (! isset($this->Contents)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new self($this->name, $this->attributes, \array_map(
|
||||
function (Renderable $R) use ($Transform): Renderable {
|
||||
if (! $R instanceof TransformableRenderable) {
|
||||
return $R;
|
||||
}
|
||||
|
||||
return $R->transformingContent($Transform);
|
||||
},
|
||||
$this->Contents
|
||||
));
|
||||
}
|
||||
|
||||
public function replacingAll(string $search, TransformableRenderable $Replacement): TransformableRenderable
|
||||
{
|
||||
if (! isset($this->Contents)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return new self($this->name, $this->attributes, \array_map(
|
||||
function (Renderable $R) use ($search, $Replacement): Renderable {
|
||||
if (! $R instanceof TransformableRenderable) {
|
||||
return $R;
|
||||
}
|
||||
|
||||
return $R->replacingAll($search, $Replacement);
|
||||
},
|
||||
$this->Contents
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace Erusev\Parsedown\Html\Renderables;
|
||||
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\Html\Sanitisation\Escaper;
|
||||
use Erusev\Parsedown\Html\TransformableRenderable;
|
||||
|
||||
final class Text implements TransformableRenderable
|
||||
final class Text implements Renderable
|
||||
{
|
||||
use CanonicalStateRenderable;
|
||||
|
||||
@ -31,59 +31,4 @@ final class Text implements TransformableRenderable
|
||||
{
|
||||
return Escaper::htmlElementValueEscapingDoubleQuotes($this->text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Closure(string):TransformableRenderable $Transform
|
||||
* @return TransformableRenderable
|
||||
*/
|
||||
public function transformingContent(\Closure $Transform): TransformableRenderable
|
||||
{
|
||||
return $Transform($this->text);
|
||||
}
|
||||
|
||||
public function replacingAll(string $search, TransformableRenderable $Replacement): TransformableRenderable
|
||||
{
|
||||
$searchLen = \strlen($search);
|
||||
|
||||
if ($searchLen < 1) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$result = \preg_match_all(
|
||||
'/\b'.\preg_quote($search, '/').'\b/',
|
||||
$this->text,
|
||||
$matches,
|
||||
\PREG_OFFSET_CAPTURE
|
||||
);
|
||||
|
||||
if (empty($result)) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$lastEndPos = 0;
|
||||
|
||||
$Container = new Container;
|
||||
|
||||
foreach ($matches[0] as $match) {
|
||||
$pos = $match[1];
|
||||
$endPos = $pos + $searchLen;
|
||||
|
||||
if ($pos !== $lastEndPos) {
|
||||
$Container = $Container->adding(
|
||||
new Text(\substr($this->text, $lastEndPos, $pos - $lastEndPos))
|
||||
);
|
||||
}
|
||||
|
||||
$Container = $Container->adding($Replacement);
|
||||
$lastEndPos = $endPos;
|
||||
}
|
||||
|
||||
if (\strlen($this->text) !== $lastEndPos) {
|
||||
$Container = $Container->adding(
|
||||
new Text(\substr($this->text, $lastEndPos))
|
||||
);
|
||||
}
|
||||
|
||||
return $Container;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ namespace Erusev\Parsedown\Html\Sanitisation;
|
||||
|
||||
final class UrlSanitiser
|
||||
{
|
||||
private const COMMON_SCHEMES = [
|
||||
/** @var string[] */
|
||||
private static $COMMON_SCHEMES = [
|
||||
'http://',
|
||||
'https://',
|
||||
'ftp://',
|
||||
@ -32,7 +33,7 @@ final class UrlSanitiser
|
||||
public static function filter($url, $permittedSchemes = null)
|
||||
{
|
||||
if (! isset($permittedSchemes)) {
|
||||
$permittedSchemes = self::COMMON_SCHEMES;
|
||||
$permittedSchemes = self::$COMMON_SCHEMES;
|
||||
}
|
||||
|
||||
foreach ($permittedSchemes as $scheme) {
|
||||
|
@ -1,34 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Html;
|
||||
|
||||
interface TransformableRenderable extends Renderable
|
||||
{
|
||||
/**
|
||||
* Takes a closure $Transform which will provide a transformation of
|
||||
* a "contained text" into Renderables.
|
||||
*
|
||||
* In order for TransformableRenderable to make sense, a Renderable must
|
||||
* have:
|
||||
* 1. Some concept of "contained text". $Transform can be applied
|
||||
* piece-wise if your container contains logically disjoint sections
|
||||
* of text.
|
||||
* 2. A generic mechanism for containing other Renderables, or replacing
|
||||
* the current renderable with a container.
|
||||
*
|
||||
* It is acceptable to only partially transform "contained text".
|
||||
*
|
||||
* @param \Closure(string):TransformableRenderable $Transform
|
||||
* @return TransformableRenderable
|
||||
*/
|
||||
public function transformingContent(\Closure $Transform): TransformableRenderable;
|
||||
|
||||
/**
|
||||
* Similar to transformingContent, but replace the string $search in text content
|
||||
* with the renderable $Replacement and return the result.
|
||||
*
|
||||
* @param string $search
|
||||
* @return TransformableRenderable
|
||||
*/
|
||||
public function replacingAll(string $search, TransformableRenderable $Replacement): TransformableRenderable;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown;
|
||||
|
||||
/**
|
||||
* Beware that the values of MutableConfigurables are NOT stable. Values SHOULD
|
||||
* be accessed as close to use as possible. Parsing operations sharing the same
|
||||
* State SHOULD NOT be triggered between where values are read and where they
|
||||
* need to be relied upon.
|
||||
*/
|
||||
interface MutableConfigurable extends Configurable
|
||||
{
|
||||
/**
|
||||
* Objects contained in State can generally be regarded as immutable,
|
||||
* however, when mutability is *required* then isolatedCopy (this method)
|
||||
* MUST be implemented to take a reliable copy of the contained state,
|
||||
* which MUST be fully seperable from the current instance. This is
|
||||
* sometimes referred to as a "deep copy".
|
||||
*
|
||||
* The following assumption is made when you implement
|
||||
* MutableConfigurable:
|
||||
*
|
||||
* A shared, (more or less) globally writable, instantaniously updating
|
||||
* (at all parsing levels), single copy of a Configurable is intentional
|
||||
* and desired.
|
||||
*
|
||||
* As such, Parsedown will use the isolatedCopy method to ensure state
|
||||
* isolation between successive parsing calls (which are considered to be
|
||||
* isolated documents).
|
||||
*
|
||||
* You MUST NOT depend on the method `initial` being called when a clean
|
||||
* parsing state is desired, this will not reliably occur; implement
|
||||
* isolatedCopy properly to allow Parsedown to manage this.
|
||||
*
|
||||
* Failing to implement this method properly can result in unintended
|
||||
* side-effects. If possible, you should design your Configurable to be
|
||||
* immutable, which allows a single copy to be shared safely, and mutations
|
||||
* localised to a heirarchy for which the order of operations is easy to
|
||||
* reason about.
|
||||
*
|
||||
* @return static
|
||||
*/
|
||||
public function isolatedCopy();
|
||||
}
|
@ -14,8 +14,8 @@ use Erusev\Parsedown\Components\StateUpdatingBlock;
|
||||
use Erusev\Parsedown\Configurables\BlockTypes;
|
||||
use Erusev\Parsedown\Configurables\InlineTypes;
|
||||
use Erusev\Parsedown\Configurables\RecursionLimiter;
|
||||
use Erusev\Parsedown\Configurables\RenderStack;
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
use Erusev\Parsedown\Parsing\Excerpt;
|
||||
use Erusev\Parsedown\Parsing\Line;
|
||||
use Erusev\Parsedown\Parsing\Lines;
|
||||
@ -29,34 +29,23 @@ final class Parsedown
|
||||
|
||||
public function __construct(StateBearer $StateBearer = null)
|
||||
{
|
||||
$State = ($StateBearer ?? new State)->state();
|
||||
$StateBearer = $StateBearer ?: new State;
|
||||
|
||||
$this->State = $State->isolatedCopy();
|
||||
$this->State = $StateBearer->state();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $markdown
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public function toHtml($markdown)
|
||||
public function text($text)
|
||||
{
|
||||
list($StateRenderables, $State) = self::lines(
|
||||
Lines::fromTextLines($markdown, 0),
|
||||
$this->State->isolatedCopy()
|
||||
Lines::fromTextLines($text, 0),
|
||||
$this->State
|
||||
);
|
||||
|
||||
$Renderables = \array_reduce(
|
||||
\array_reverse($State->get(RenderStack::class)->getStack()),
|
||||
/**
|
||||
* @param Renderable[] $Renderables
|
||||
* @param \Closure(Renderable[],State):Renderable[] $RenderMap
|
||||
* @return Renderable[]
|
||||
*/
|
||||
function (array $Renderables, \Closure $RenderMap) use ($State): array {
|
||||
return $RenderMap($Renderables, $State);
|
||||
},
|
||||
$State->applyTo($StateRenderables)
|
||||
);
|
||||
$Renderables = $State->applyTo($StateRenderables);
|
||||
|
||||
$html = self::render($Renderables);
|
||||
|
||||
@ -64,7 +53,7 @@ final class Parsedown
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{StateRenderable[], State}
|
||||
* @return array{0: StateRenderable[], 1: State}
|
||||
*/
|
||||
public static function lines(Lines $Lines, State $State)
|
||||
{
|
||||
@ -90,11 +79,11 @@ final class Parsedown
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{Block[], State}
|
||||
* @return array{0: Block[], 1: State}
|
||||
*/
|
||||
public static function blocks(Lines $Lines, State $State)
|
||||
{
|
||||
$RecursionLimiter = $State->get(RecursionLimiter::class)->incremented();
|
||||
$RecursionLimiter = $State->get(RecursionLimiter::class)->increment();
|
||||
|
||||
if ($RecursionLimiter->isDepthExceeded()) {
|
||||
$State = $State->setting(new BlockTypes([], []));
|
||||
@ -200,7 +189,7 @@ final class Parsedown
|
||||
# standardize line breaks
|
||||
$text = \str_replace(["\r\n", "\r"], "\n", $text);
|
||||
|
||||
$RecursionLimiter = $State->get(RecursionLimiter::class)->incremented();
|
||||
$RecursionLimiter = $State->get(RecursionLimiter::class)->increment();
|
||||
|
||||
if ($RecursionLimiter->isDepthExceeded()) {
|
||||
return [Plaintext::build(new Excerpt($text, 0), $State)];
|
||||
|
@ -7,21 +7,21 @@ final class Context
|
||||
/** @var Line */
|
||||
private $Line;
|
||||
|
||||
/** @var int|null */
|
||||
private $precedingEmptyLines;
|
||||
/** @var int */
|
||||
private $previousEmptyLines;
|
||||
|
||||
/** @var string */
|
||||
private $precedingEmptyLinesText;
|
||||
private $previousEmptyLinesText;
|
||||
|
||||
/**
|
||||
* @param Line $Line
|
||||
* @param string $precedingEmptyLinesText
|
||||
* @param string $previousEmptyLinesText
|
||||
*/
|
||||
public function __construct($Line, $precedingEmptyLinesText)
|
||||
public function __construct($Line, $previousEmptyLinesText)
|
||||
{
|
||||
$this->Line = $Line;
|
||||
$this->precedingEmptyLinesText = $precedingEmptyLinesText;
|
||||
$this->precedingEmptyLines = null;
|
||||
$this->previousEmptyLinesText = $previousEmptyLinesText;
|
||||
$this->previousEmptyLines = \substr_count($previousEmptyLinesText, "\n");
|
||||
}
|
||||
|
||||
/** @return Line */
|
||||
@ -31,18 +31,14 @@ final class Context
|
||||
}
|
||||
|
||||
/** @return int */
|
||||
public function precedingEmptyLines()
|
||||
public function previousEmptyLines()
|
||||
{
|
||||
if (! isset($this->precedingEmptyLines)) {
|
||||
$this->precedingEmptyLines = \substr_count($this->precedingEmptyLinesText, "\n");
|
||||
}
|
||||
|
||||
return $this->precedingEmptyLines;
|
||||
return $this->previousEmptyLines;
|
||||
}
|
||||
|
||||
/** @return string */
|
||||
public function precedingEmptyLinesText()
|
||||
public function previousEmptyLinesText()
|
||||
{
|
||||
return $this->precedingEmptyLinesText;
|
||||
return $this->previousEmptyLinesText;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ final class Lines
|
||||
|
||||
if (! $containsBlankLines) {
|
||||
foreach ($Contexts as $Context) {
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
$containsBlankLines = true;
|
||||
break;
|
||||
}
|
||||
@ -57,33 +57,33 @@ final class Lines
|
||||
$text = \str_replace(["\r\n", "\r"], "\n", $text);
|
||||
|
||||
$Contexts = [];
|
||||
$sequentialEmptyLines = '';
|
||||
$sequentialLines = '';
|
||||
|
||||
foreach (\explode("\n", $text) as $line) {
|
||||
if (\chop($line) === '') {
|
||||
$sequentialEmptyLines .= $line . "\n";
|
||||
$sequentialLines .= $line . "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
$Contexts[] = new Context(
|
||||
new Line($line, $indentOffset),
|
||||
$sequentialEmptyLines
|
||||
$sequentialLines
|
||||
);
|
||||
|
||||
$sequentialEmptyLines = '';
|
||||
$sequentialLines = '';
|
||||
}
|
||||
|
||||
return new self($Contexts, $sequentialEmptyLines);
|
||||
return new self($Contexts, $sequentialLines);
|
||||
}
|
||||
|
||||
/** @return bool */
|
||||
public function isEmpty()
|
||||
{
|
||||
return ! $this->containsBlankLines && \count($this->Contexts) === 0;
|
||||
return \count($this->Contexts) === 0 && $this->trailingBlankLines === 0;
|
||||
}
|
||||
|
||||
/** @return Context[] */
|
||||
public function contexts()
|
||||
public function Contexts()
|
||||
{
|
||||
return $this->Contexts;
|
||||
}
|
||||
@ -140,7 +140,7 @@ final class Lines
|
||||
|
||||
$NextLines->Contexts[0] = new Context(
|
||||
$NextLines->Contexts[0]->line(),
|
||||
$NextLines->Contexts[0]->precedingEmptyLinesText() . $Lines->trailingBlankLinesText
|
||||
$NextLines->Contexts[0]->previousEmptyLinesText() . $Lines->trailingBlankLinesText
|
||||
);
|
||||
|
||||
$Lines->Contexts = \array_merge($Lines->Contexts, $NextLines->Contexts);
|
||||
@ -162,10 +162,10 @@ final class Lines
|
||||
|
||||
$Context = new Context(
|
||||
$Context->line(),
|
||||
$Context->precedingEmptyLinesText() . $Lines->trailingBlankLinesText
|
||||
$Context->previousEmptyLinesText() . $Lines->trailingBlankLinesText
|
||||
);
|
||||
|
||||
if ($Context->precedingEmptyLines() > 0) {
|
||||
if ($Context->previousEmptyLines() > 0) {
|
||||
$Lines->containsBlankLines = true;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,7 @@ final class State implements StateBearer
|
||||
/**
|
||||
* @var array<class-string<Configurable>, Configurable>
|
||||
*/
|
||||
private static $initialCache = [];
|
||||
private static $initialCache;
|
||||
|
||||
/**
|
||||
* @param Configurable[] $Configurables
|
||||
@ -24,7 +24,7 @@ final class State implements StateBearer
|
||||
{
|
||||
$this->state = \array_combine(
|
||||
\array_map(
|
||||
/** @return class-string<Configurable> */
|
||||
/** @return class-string */
|
||||
function (Configurable $C) { return \get_class($C); },
|
||||
$Configurables
|
||||
),
|
||||
@ -50,34 +50,21 @@ final class State implements StateBearer
|
||||
|
||||
/**
|
||||
* @template T as Configurable
|
||||
* @template-typeof T $className
|
||||
* @param class-string<T> $className
|
||||
* @template-typeof T $configurableClass
|
||||
* @param class-string<Configurable> $configurableClass
|
||||
* @return T
|
||||
*/
|
||||
public function get($className)
|
||||
public function get($configurableClass)
|
||||
{
|
||||
if (
|
||||
! isset($this->state[$className])
|
||||
&& \is_subclass_of($className, MutableConfigurable::class, true)
|
||||
) {
|
||||
if (! isset(self::$initialCache[$className])) {
|
||||
/** @var T */
|
||||
self::$initialCache[$className] = $className::initial();
|
||||
}
|
||||
|
||||
/**
|
||||
* @var T
|
||||
* @psalm-suppress PossiblyUndefinedMethod
|
||||
*/
|
||||
$this->state[$className] = self::$initialCache[$className]->isolatedCopy();
|
||||
if (isset($this->state[$configurableClass])) {
|
||||
return $this->state[$configurableClass];
|
||||
}
|
||||
|
||||
/** @var T */
|
||||
return (
|
||||
$this->state[$className]
|
||||
?? self::$initialCache[$className]
|
||||
?? self::$initialCache[$className] = $className::initial()
|
||||
);
|
||||
if (! isset(self::$initialCache[$configurableClass])) {
|
||||
self::$initialCache[$configurableClass] = $configurableClass::initial();
|
||||
}
|
||||
|
||||
return self::$initialCache[$configurableClass];
|
||||
}
|
||||
|
||||
public function __clone()
|
||||
@ -102,24 +89,11 @@ final class State implements StateBearer
|
||||
);
|
||||
}
|
||||
|
||||
public function state(): State
|
||||
/**
|
||||
* @return State
|
||||
*/
|
||||
public function state()
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @return self */
|
||||
public static function from(StateBearer $StateBearer)
|
||||
{
|
||||
return $StateBearer->state();
|
||||
}
|
||||
|
||||
public function isolatedCopy(): self
|
||||
{
|
||||
return new self(\array_map(
|
||||
function ($C) {
|
||||
return $C instanceof MutableConfigurable ? $C->isolatedCopy() : $C;
|
||||
},
|
||||
$this->state
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ namespace Erusev\Parsedown;
|
||||
|
||||
interface StateBearer
|
||||
{
|
||||
public function state(): State;
|
||||
/** @return static */
|
||||
public static function from(StateBearer $StateBearer);
|
||||
/**
|
||||
* @return State
|
||||
*/
|
||||
public function state();
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class CommonMarkTestStrict extends TestCase
|
||||
*/
|
||||
public function testExample($_, $__, $markdown, $expectedHtml)
|
||||
{
|
||||
$actualHtml = $this->Parsedown->toHtml($markdown);
|
||||
$actualHtml = $this->Parsedown->text($markdown);
|
||||
$this->assertEquals($expectedHtml, $actualHtml);
|
||||
}
|
||||
|
||||
|
@ -28,13 +28,18 @@ class CommonMarkTestWeak extends CommonMarkTestStrict
|
||||
*/
|
||||
public function __construct($name = null, array $data = [], $dataName = '')
|
||||
{
|
||||
$textLevelElements = \array_keys(Element::TEXT_LEVEL_ELEMENTS);
|
||||
$textLevelElements = \array_keys(Element::$TEXT_LEVEL_ELEMENTS);
|
||||
|
||||
$textLevelElements = \array_map(
|
||||
function ($e) { return \preg_quote($e, '/'); },
|
||||
$textLevelElements
|
||||
\array_walk(
|
||||
$textLevelElements,
|
||||
/**
|
||||
* @param string &$element
|
||||
* @return void
|
||||
*/
|
||||
function (&$element) {
|
||||
$element = \preg_quote($element, '/');
|
||||
}
|
||||
);
|
||||
|
||||
$this->textLevelElementRegex = '\b(?:' . \implode('|', $textLevelElements) . ')\b';
|
||||
|
||||
parent::__construct($name, $data, $dataName);
|
||||
@ -54,7 +59,7 @@ class CommonMarkTestWeak extends CommonMarkTestStrict
|
||||
{
|
||||
$expectedHtml = $this->cleanupHtml($expectedHtml);
|
||||
|
||||
$actualHtml = $this->Parsedown->toHtml($markdown);
|
||||
$actualHtml = $this->Parsedown->text($markdown);
|
||||
$actualHtml = $this->cleanupHtml($actualHtml);
|
||||
|
||||
$this->assertEquals($expectedHtml, $actualHtml);
|
||||
|
@ -6,13 +6,11 @@ use Erusev\Parsedown\Components\Blocks\Markup as BlockMarkup;
|
||||
use Erusev\Parsedown\Components\Inlines\Markup as InlineMarkup;
|
||||
use Erusev\Parsedown\Configurables\BlockTypes;
|
||||
use Erusev\Parsedown\Configurables\Breaks;
|
||||
use Erusev\Parsedown\Configurables\HeaderSlug;
|
||||
use Erusev\Parsedown\Configurables\InlineTypes;
|
||||
use Erusev\Parsedown\Configurables\SafeMode;
|
||||
use Erusev\Parsedown\Configurables\StrictMode;
|
||||
use Erusev\Parsedown\Parsedown;
|
||||
use Erusev\Parsedown\State;
|
||||
use Erusev\Parsedown\StateBearer;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ParsedownTest extends TestCase
|
||||
@ -40,16 +38,6 @@ class ParsedownTest extends TestCase
|
||||
return [\dirname(__FILE__).'/data/'];
|
||||
}
|
||||
|
||||
protected function initState(string $testName): StateBearer
|
||||
{
|
||||
return new State([
|
||||
new SafeMode(\substr($testName, 0, 3) === 'xss'),
|
||||
new StrictMode(\substr($testName, 0, 6) === 'strict'),
|
||||
new Breaks(\substr($testName, 0, 14) === 'breaks_enabled'),
|
||||
new HeaderSlug(\substr($testName, 0, 4) === 'slug'),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider data
|
||||
* @param string $test
|
||||
@ -67,21 +55,28 @@ class ParsedownTest extends TestCase
|
||||
$expectedMarkup = \str_replace("\r\n", "\n", $expectedMarkup);
|
||||
$expectedMarkup = \str_replace("\r", "\n", $expectedMarkup);
|
||||
|
||||
$Parsedown = new Parsedown($this->initState($test));
|
||||
$Parsedown = new Parsedown(new State([
|
||||
new SafeMode(\substr($test, 0, 3) === 'xss'),
|
||||
new StrictMode(\substr($test, 0, 6) === 'strict'),
|
||||
new Breaks(\substr($test, 0, 14) === 'breaks_enabled'),
|
||||
]));
|
||||
|
||||
$actualMarkup = $Parsedown->toHtml($markdown);
|
||||
$actualMarkup = $Parsedown->text($markdown);
|
||||
|
||||
$this->assertEquals($expectedMarkup, $actualMarkup);
|
||||
}
|
||||
|
||||
/** @return array<int, array{0:string, 1:string}> */
|
||||
/** @return array<int, array{0:string, 1:string} */
|
||||
public function data()
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($this->dirs as $dir) {
|
||||
$Folder = new \DirectoryIterator($dir);
|
||||
|
||||
foreach ($Folder as $File) {
|
||||
/** @var $File DirectoryIterator */
|
||||
|
||||
if (! $File->isFile()) {
|
||||
continue;
|
||||
}
|
||||
@ -159,6 +154,6 @@ EXPECTED_HTML;
|
||||
InlineTypes::initial()->removing([InlineMarkup::class]),
|
||||
]));
|
||||
|
||||
$this->assertEquals($expectedHtml, $parsedownWithNoMarkup->toHtml($markdownWithHtml));
|
||||
$this->assertEquals($expectedHtml, $parsedownWithNoMarkup->text($markdownWithHtml));
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +0,0 @@
|
||||
<div id="foo"
|
||||
class="bar">
|
||||
</div>
|
@ -1,3 +0,0 @@
|
||||
<div id="foo"
|
||||
class="bar">
|
||||
</div>
|
@ -1,3 +1,3 @@
|
||||
<div id="foo" class="bar
|
||||
baz">
|
||||
</div>
|
||||
<div>
|
||||
*foo*
|
||||
<p><em>bar</em></p>
|
@ -1,3 +1,4 @@
|
||||
<div id="foo" class="bar
|
||||
baz">
|
||||
</div>
|
||||
<div>
|
||||
*foo*
|
||||
|
||||
*bar*
|
@ -1,2 +0,0 @@
|
||||
<div id="foo"
|
||||
*hi*
|
@ -1,2 +0,0 @@
|
||||
<div id="foo"
|
||||
*hi*
|
@ -1,2 +0,0 @@
|
||||
<div class
|
||||
foo
|
@ -1,2 +0,0 @@
|
||||
<div class
|
||||
foo
|
@ -1,2 +1 @@
|
||||
<div *???-&&&-<---
|
||||
*foo*
|
||||
<div><a href="bar">*foo*</a></div>
|
@ -1,2 +1 @@
|
||||
<div *???-&&&-<---
|
||||
*foo*
|
||||
<div><a href="bar">*foo*</a></div>
|
@ -1,3 +1,3 @@
|
||||
<a href="foo">
|
||||
<Warning>
|
||||
*bar*
|
||||
</a>
|
||||
</Warning>
|
@ -1,3 +1,3 @@
|
||||
<a href="foo">
|
||||
<Warning>
|
||||
*bar*
|
||||
</a>
|
||||
</Warning>
|
@ -1,3 +0,0 @@
|
||||
<i class="foo">
|
||||
*bar*
|
||||
</i>
|
@ -1,3 +0,0 @@
|
||||
<i class="foo">
|
||||
*bar*
|
||||
</i>
|
@ -1,2 +0,0 @@
|
||||
</ins>
|
||||
*bar*
|
@ -1,2 +0,0 @@
|
||||
</ins>
|
||||
*bar*
|
@ -1,3 +0,0 @@
|
||||
<del>
|
||||
*foo*
|
||||
</del>
|
@ -1,3 +0,0 @@
|
||||
<del>
|
||||
*foo*
|
||||
</del>
|
@ -1,3 +1 @@
|
||||
<del>
|
||||
<p><em>foo</em></p>
|
||||
</del>
|
||||
<p><del><em>foo</em></del></p>
|
@ -1,5 +1 @@
|
||||
<del>
|
||||
|
||||
*foo*
|
||||
|
||||
</del>
|
||||
<del>*foo*</del>
|
@ -1 +0,0 @@
|
||||
<a href="/bar\/)">
|
@ -1 +0,0 @@
|
||||
<a href="/bar\/)">
|
@ -1 +1 @@
|
||||
<a href="öö.html">
|
||||
<p><code>f&ouml;&ouml;</code></p>
|
@ -1 +1 @@
|
||||
<a href="öö.html">
|
||||
`föö`
|
@ -1 +1 @@
|
||||
<p><a href="/uri">foo *bar</a>*</p>
|
||||
<p><a href="/uri">foo *bar</a></p>
|
@ -1,3 +1,3 @@
|
||||
[foo *bar][ref]*
|
||||
[foo *bar][ref]
|
||||
|
||||
[ref]: /uri
|
@ -334,28 +334,4 @@ bar
|
||||
<p>foo</p>
|
||||
<body> foo
|
||||
<p>bar</p>
|
||||
<p>foo</p>
|
||||
<hr />
|
||||
<img>
|
||||
</script>
|
||||
bar
|
||||
<p>foo</p>
|
||||
<img>
|
||||
-->
|
||||
bar
|
||||
<p>foo</p>
|
||||
<img>
|
||||
?>
|
||||
bar
|
||||
<p>foo</p>
|
||||
<img>
|
||||
>
|
||||
bar
|
||||
<p>foo</p>
|
||||
<img>
|
||||
]]>
|
||||
bar
|
||||
<p>foo</p>
|
||||
<img>
|
||||
<p>bar</p>
|
||||
<p>foo</p>
|
@ -480,42 +480,4 @@ foo
|
||||
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
---
|
||||
|
||||
<img>
|
||||
</script>
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
<img>
|
||||
-->
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
<img>
|
||||
?>
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
<img>
|
||||
>
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
<img>
|
||||
]]>
|
||||
bar
|
||||
|
||||
foo
|
||||
|
||||
<img>
|
||||
|
||||
bar
|
||||
|
||||
foo
|
@ -1,11 +0,0 @@
|
||||
<h1 id="foo">foo</h1>
|
||||
<h1 id="foo-bar">foo bar</h1>
|
||||
<h1 id="foobar">foo_bar</h1>
|
||||
<h1 id="foobar-1">foo+bar-1</h1>
|
||||
<h1 id="foobar-2">foo+bar</h1>
|
||||
<h1 id="2rer0ගම්මැද්ද-v-force-ඉනොවේශන්-නේෂන්-සඳහා-එවූ-නි">2rer*(0👍ගම්මැද්ද V FORCE ඉනොවේශන් නේෂන් සඳහා එවූ නි</h1>
|
||||
<h2 id="foo-1">foo</h2>
|
||||
<h2 id="foo-bar-1">foo bar</h2>
|
||||
<h2 id="foobar-3">foo_bar</h2>
|
||||
<h2 id="foobar-4">foo+bar</h2>
|
||||
<h2 id="2rer0ගම්මැද්ද-v-force-ඉනොවේශන්-නේෂන්-සඳහා-එවූ-නි-1">2rer*(0👍ගම්මැද්ද V FORCE ඉනොවේශන් නේෂන් සඳහා එවූ නි</h2>
|
@ -1,26 +0,0 @@
|
||||
# foo
|
||||
|
||||
# foo bar
|
||||
|
||||
# foo_bar
|
||||
|
||||
# foo+bar-1
|
||||
|
||||
# foo+bar
|
||||
|
||||
# 2rer*(0👍ගම්මැද්ද V FORCE ඉනොවේශන් නේෂන් සඳහා එවූ නි
|
||||
|
||||
foo
|
||||
---
|
||||
|
||||
foo bar
|
||||
---
|
||||
|
||||
foo_bar
|
||||
---
|
||||
|
||||
foo+bar
|
||||
---
|
||||
|
||||
2rer*(0👍ගම්මැද්ද V FORCE ඉනොවේශන් නේෂන් සඳහා එවූ නි
|
||||
---
|
@ -1,9 +1,3 @@
|
||||
<p>an autolink <a href="http://example.com">http://example.com</a></p>
|
||||
<p>inside of parentheses (<a href="http://example.com">http://example.com</a>)</p>
|
||||
<p><a href="http://www.google.com/search?q=Markup+(business)">http://www.google.com/search?q=Markup+(business)</a></p>
|
||||
<p><a href="http://www.google.com/search?q=Markup+(business)">http://www.google.com/search?q=Markup+(business)</a>))</p>
|
||||
<p>(<a href="http://www.google.com/search?q=Markup+(business)">http://www.google.com/search?q=Markup+(business)</a>)</p>
|
||||
<p>(<a href="http://www.google.com/search?q=Markup+(business)">http://www.google.com/search?q=Markup+(business)</a></p>
|
||||
<p>trailing slash <a href="http://example.com/">http://example.com/</a> and <a href="http://example.com/path/">http://example.com/path/</a></p>
|
||||
<p>trailing paren <a href="https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)">https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)</a></p>
|
||||
<p>complex link <a href="http://elk.canda.biz/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'deve-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'*')),sort:!('@timestamp',desc))">http://elk.canda.biz/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'deve-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'*')),sort:!('@timestamp',desc))</a></p>
|
||||
<p>trailing slash <a href="http://example.com/">http://example.com/</a> and <a href="http://example.com/path/">http://example.com/path/</a></p>
|
@ -2,16 +2,4 @@ an autolink http://example.com
|
||||
|
||||
inside of parentheses (http://example.com)
|
||||
|
||||
http://www.google.com/search?q=Markup+(business)
|
||||
|
||||
http://www.google.com/search?q=Markup+(business)))
|
||||
|
||||
(http://www.google.com/search?q=Markup+(business))
|
||||
|
||||
(http://www.google.com/search?q=Markup+(business)
|
||||
|
||||
trailing slash http://example.com/ and http://example.com/path/
|
||||
|
||||
trailing paren https://www.owasp.org/index.php/Cross-site_Scripting_(XSS)
|
||||
|
||||
complex link http://elk.canda.biz/app/kibana#/discover?_g=()&_a=(columns:!(_source),index:'deve-*',interval:auto,query:(query_string:(analyze_wildcard:!t,query:'*')),sort:!('@timestamp',desc))
|
||||
trailing slash http://example.com/ and http://example.com/path/
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace Erusev\Parsedown\Tests\Configurables;
|
||||
|
||||
use Erusev\Parsedown\Components\Blocks\FencedCode;
|
||||
use Erusev\Parsedown\Components\Blocks\IndentedCode;
|
||||
use Erusev\Parsedown\Components\Blocks\Markup;
|
||||
use Erusev\Parsedown\Components\Blocks\Rule;
|
||||
@ -15,7 +14,6 @@ final class BlockTypesTest extends TestCase
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
* @psalm-suppress InvalidDocblock
|
||||
*/
|
||||
public function testAddingTypes()
|
||||
{
|
||||
@ -43,9 +41,5 @@ final class BlockTypesTest extends TestCase
|
||||
$BlockTypes = $BlockTypes->addingUnmarkedLowPrecedence([IndentedCode::class]);
|
||||
$this->assertSame([Markup::class, IndentedCode::class, Rule::class], $BlockTypes->markedBy('@'));
|
||||
$this->assertSame([Rule::class, Markup::class, IndentedCode::class], $BlockTypes->unmarked());
|
||||
|
||||
$BlockTypes = $BlockTypes->replacing(IndentedCode::class, FencedCode::class);
|
||||
$this->assertSame([Markup::class, FencedCode::class, Rule::class], $BlockTypes->markedBy('@'));
|
||||
$this->assertSame([Rule::class, Markup::class, FencedCode::class], $BlockTypes->unmarked());
|
||||
}
|
||||
}
|
||||
|
@ -1,60 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Tests\Configurables;
|
||||
|
||||
use Erusev\Parsedown\Configurables\HeaderSlug;
|
||||
use Erusev\Parsedown\Configurables\SlugRegister;
|
||||
use Erusev\Parsedown\State;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class HeaderSlugTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testNamedConstructor()
|
||||
{
|
||||
$State = new State([HeaderSlug::enabled()]);
|
||||
|
||||
$this->assertSame(true, $State->get(HeaderSlug::class)->isEnabled());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testCustomCallback()
|
||||
{
|
||||
$HeaderSlug = HeaderSlug::withCallback(function (string $t): string {
|
||||
return \preg_replace('/[^A-Za-z0-9]++/', '_', $t);
|
||||
});
|
||||
|
||||
$this->assertSame(
|
||||
'foo_bar',
|
||||
$HeaderSlug->transform(SlugRegister::initial(), 'foo bar')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testCustomDuplicationCallback()
|
||||
{
|
||||
$HeaderSlug = HeaderSlug::withDuplicationCallback(function (string $t, int $n): string {
|
||||
return $t . '_' . \strval($n-1);
|
||||
});
|
||||
|
||||
$SlugRegister = new SlugRegister;
|
||||
$HeaderSlug->transform($SlugRegister, 'foo bar');
|
||||
|
||||
$this->assertSame(
|
||||
'foo-bar_1',
|
||||
$HeaderSlug->transform($SlugRegister, 'foo bar')
|
||||
);
|
||||
}
|
||||
}
|
@ -5,7 +5,6 @@ namespace Erusev\Parsedown\Tests\Configurables;
|
||||
use Erusev\Parsedown\Components\Inlines\Code;
|
||||
use Erusev\Parsedown\Components\Inlines\Emphasis;
|
||||
use Erusev\Parsedown\Components\Inlines\Link;
|
||||
use Erusev\Parsedown\Components\Inlines\Url;
|
||||
use Erusev\Parsedown\Configurables\InlineTypes;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
@ -29,8 +28,5 @@ final class InlineTypesTest extends TestCase
|
||||
|
||||
$InlineTypes = $InlineTypes->addingLowPrecedence('@', [Link::class]);
|
||||
$this->assertSame([Code::class, Emphasis::class, Link::class], $InlineTypes->markedBy('@'));
|
||||
|
||||
$InlineTypes = $InlineTypes->replacing(Link::class, Url::class);
|
||||
$this->assertSame([Code::class, Emphasis::class, Url::class], $InlineTypes->markedBy('@'));
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ final class RecursionLimiterTest extends TestCase
|
||||
. '<p>foo</p>'
|
||||
. \str_repeat("\n</blockquote>", 3)
|
||||
),
|
||||
$Parsedown->toHtml($borderline)
|
||||
$Parsedown->text($borderline)
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
@ -39,7 +39,7 @@ final class RecursionLimiterTest extends TestCase
|
||||
. '<p>> foo</p>'
|
||||
. \str_repeat("\n</blockquote>", 3)
|
||||
),
|
||||
$Parsedown->toHtml($exceeded)
|
||||
$Parsedown->text($exceeded)
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
@ -48,7 +48,7 @@ final class RecursionLimiterTest extends TestCase
|
||||
. '<p>fo*o*</p>'
|
||||
. \str_repeat("\n</blockquote>", 3)
|
||||
),
|
||||
$Parsedown->toHtml($exceededByInline)
|
||||
$Parsedown->text($exceededByInline)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,48 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Erusev\Parsedown\Tests\Configurables;
|
||||
|
||||
use Erusev\Parsedown\Configurables\RenderStack;
|
||||
use Erusev\Parsedown\Html\Renderable;
|
||||
use Erusev\Parsedown\Html\Renderables\Text;
|
||||
use Erusev\Parsedown\Parsedown;
|
||||
use Erusev\Parsedown\State;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class RenderStackTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testRenderStack()
|
||||
{
|
||||
$State = new State;
|
||||
$RenderStack = $State->get(RenderStack::class)
|
||||
->push(
|
||||
/**
|
||||
* @param Renderable[] $Rs
|
||||
* @param State $_
|
||||
* @return Renderable[]
|
||||
*/
|
||||
function ($Rs, $_) { return \array_merge($Rs, [new Text('baz')]); }
|
||||
)
|
||||
->push(
|
||||
/**
|
||||
* @param Renderable[] $Rs
|
||||
* @param State $_
|
||||
* @return Renderable[]
|
||||
*/
|
||||
function ($Rs, $_) { return \array_merge($Rs, [new Text('bar')]); }
|
||||
)
|
||||
;
|
||||
|
||||
$State = $State->setting($RenderStack);
|
||||
|
||||
$this->assertSame(
|
||||
"<p>foo</p>\nbar\nbaz",
|
||||
(new Parsedown($State))->toHtml('foo')
|
||||
);
|
||||
}
|
||||
}
|
@ -21,15 +21,11 @@ final class ContainerTest extends TestCase
|
||||
new Text('bar'),
|
||||
]);
|
||||
|
||||
$Container = $Container->adding(new Text('boo'));
|
||||
|
||||
$Contents = $Container->contents();
|
||||
|
||||
$this->assertTrue($Contents[0] instanceof Element);
|
||||
$this->assertSame($Contents[0]->name(), 'foo');
|
||||
$this->assertTrue($Contents[1] instanceof Text);
|
||||
$this->assertSame($Contents[1]->getHtml(), 'bar');
|
||||
$this->assertTrue($Contents[2] instanceof Text);
|
||||
$this->assertSame($Contents[2]->getHtml(), 'boo');
|
||||
}
|
||||
}
|
||||
|
@ -32,23 +32,11 @@ final class StateTest extends TestCase
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\Exception
|
||||
* @throws \PHPUnit\Framework\ExpectationFailedException
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testStateCloneVisibility()
|
||||
{
|
||||
$this->assertInstanceOf(State::class, clone(new State));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \PHPUnit\Framework\Exception
|
||||
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
|
||||
*/
|
||||
public function testStateFromStateIdentical()
|
||||
{
|
||||
$State = new State;
|
||||
|
||||
$this->assertSame($State, State::from($State));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user