laravel-6 support

This commit is contained in:
RafficMohammed
2023-01-08 01:17:22 +05:30
parent 1a5c16ae4b
commit 774eed8b0e
4962 changed files with 279380 additions and 297961 deletions

View File

@@ -0,0 +1,34 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPSTORM_META
{
expectedArguments(\League\CommonMark\HtmlElement::__construct(), 0, 'a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kdb', 'keygen', 'label', 'legend', 'li', 'link', 'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr');
expectedArguments(\League\CommonMark\Block\Element\Heading::__construct(), 0, 1, 2, 3, 4, 5, 6);
expectedReturnValues(\League\CommonMark\Block\Element\Heading::getLevel(), 1, 2, 3, 4, 5, 6);
registerArgumentsSet('league_commonmark_htmlblock_types', \League\CommonMark\Block\Element\HtmlBlock::TYPE_1_CODE_CONTAINER, \League\CommonMark\Block\Element\HtmlBlock::TYPE_2_COMMENT, \League\CommonMark\Block\Element\HtmlBlock::TYPE_3, \League\CommonMark\Block\Element\HtmlBlock::TYPE_4, \League\CommonMark\Block\Element\HtmlBlock::TYPE_5_CDATA, \League\CommonMark\Block\Element\HtmlBlock::TYPE_6_BLOCK_ELEMENT, \League\CommonMark\Block\Element\HtmlBlock::TYPE_7_MISC_ELEMENT);
expectedArguments(\League\CommonMark\Block\Element\HtmlBlock::__construct(), 0, argumentsSet('league_commonmark_htmlblock_types'));
expectedArguments(\League\CommonMark\Block\Element\HtmlBlock::setType(), 0, argumentsSet('league_commonmark_htmlblock_types'));
expectedReturnValues(\League\CommonMark\Block\Element\HtmlBlock::getType(), argumentsSet('league_commonmark_htmlblock_types'));
expectedArguments(\League\CommonMark\Util\RegexHelper::getHtmlBlockOpenRegex(), 0, argumentsSet('league_commonmark_htmlblock_types'));
expectedArguments(\League\CommonMark\Util\RegexHelper::getHtmlBlockCloseRegex(), 0, argumentsSet('league_commonmark_htmlblock_types'));
registerArgumentsSet('league_commonmark_newline_types', \League\CommonMark\Inline\Element\Newline::HARDBREAK, \League\CommonMark\Inline\Element\Newline::SOFTBREAK);
expectedArguments(\League\CommonMark\Inline\Element\Newline::__construct(), 0, argumentsSet('league_commonmark_newline_types'));
expectedReturnValues(\League\CommonMark\Inline\Element\Newline::getType(), argumentsSet('league_commonmark_newline_types'));
registerArgumentsSet('league_commonmark_options', 'renderer', 'commonmark', 'commonmark/enable_em', 'commonmark/enable_strong', 'commonmark/use_asterisk', 'commonmark/use_underscore', 'commonmark/unordered_list_markers', 'html_input', 'allow_unsafe_links', 'max_nesting_level', 'external_link', 'external_link/nofollow', 'external_link/noopener', 'external_link/noreferrer', 'footnote', 'footnote/backref_class', 'footnote/container_add_hr', 'footnote/container_class', 'footnote/ref_class', 'footnote/ref_id_prefix', 'footnote/footnote_class', 'footnote/footnote_id_prefix', 'heading_permalink', 'heading_permalink/html_class', 'heading_permalink/id_prefix', 'heading_permalink/inner_contents', 'heading_permalink/insert', 'heading_permalink/slug_normalizer', 'heading_permalink/symbol', 'heading_permalink/title', 'table_of_contents', 'table_of_contents/style', 'table_of_contents/normalize', 'table_of_contents/position', 'table_of_contents/html_class', 'table_of_contents/min_heading_level', 'table_of_contents/max_heading_level', 'table_of_contents/placeholder');
expectedArguments(\League\CommonMark\EnvironmentInterface::getConfig(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\CommonMark\Util\ConfigurationInterface::get(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\CommonMark\Util\ConfigurationInterface::set(), 0, argumentsSet('league_commonmark_options'));
}

View File

@@ -0,0 +1,842 @@
# Change Log
All notable changes to this project will be documented in this file.
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [0.19.3] - 2019-06-18
### Fixed
- Fixed bug where elements with content of `"0"` wouldn't be rendered (#376)
## [0.19.2] - 2019-05-19
### Fixed
- Fixed bug where default values for nested configuration paths were inadvertently cast to strings
## [0.19.1] - 2019-04-10
### Added
- Added the missing `addExtension()` method to the new `ConfigurableEnvironmentInterface`
### Fixed
- Fixed extensions not being able to register other extensions
## [0.19.0] - 2019-04-10
### Added
- The priority of parsers, processors, and renderers can now be set when `add()`ing them; you no longer need to rely on the order in which they are added
- Added support for trying multiple parsers per block/inline
- Extracted two new base interfaces from `Environment`:
- `EnvironmentInterface`
- `ConfigurableEnvironmentInterface`
- Extracted a new `AbstractStringContainerBlock` base class and corresponding `StringContainerInterface` from `AbstractBlock`
- Added `Cursor::getEncoding()` method
- Added `.phpstorm.meta.php` file for better IDE code completion
- Made some minor optimizations here and there
### Changed
- Pretty much everything now has parameter and return types (#346)
- Attributes passed to `HtmlElement` will now be escaped by default
- `Environment` is now a `final` class
- `Environment::getBlockRendererForClass()` was replaced with `Environment::getBlockRenderersForClass()` (note the added `s`)
- `Environment::getInlineRendererForClass()` was replaced with `Environment::getInlineRenderersForClass()` (note the added `s`)
- The `Environment::get____()` methods now return an iterator instead of an array
- `Context::addBlock()` no longer returns the same block instance you passed into the method, as this served no useful purpose
- `RegexHelper::isEscapable()` no longer accepts `null` values
- `Node::replaceChildren()` now accepts any type of `iterable`, not just `array`s
- Some block elements now extend `AbstractStringContainerBlock` instead of `AbstractBlock`
- `InlineContainerInterface` now extends the new `StringContainerInterface`
- The `handleRemainingContents()` method (formerly on `AbstractBlock`, now on `AbstractStringContainerBlock`) is now an `abstract method
- The `InlineParserContext` constructor now requires an `AbstractStringContainerBlock` instead of an `AbstractBlock`
### Removed
- Removed support for PHP 5.6 and 7.0 (#346)
- Removed support for `add()`ing parsers with just the target block/inline class name - you need to include the full namespace now
- Removed the following unused methods from `Environment`:
- `getInlineParser($name)`
- `getInlineParsers()`
- `createInlineParserEngine()`
- Removed the unused `getName()` methods:
- `AbstractBlockParser::getName()`
- `AbstractInlineParser::getName()`
- `BlockParserInterface::getName()`
- `InlinerParserInterface::getName()`
- Removed the now-useless classes:
- `AbstractBlockParser`
- `AbstractInlinerParser`
- `InlineContainer`
- Removed the `AbstractBlock::acceptsLines()` method
- Removed the now-useless constructor from `AbstractBlock`
- Removed previously-deprecated functionality:
- `InlineContainer` class
- `RegexHelper::$instance`
- `RegexHelper::getInstance()`
- `RegexHelper::getPartialRegex()`
- `RegexHelper::getHtmlTagRegex()`
- `RegexHelper::getLinkTitleRegex()`
- `RegexHelper::getLinkDestinationBracesRegex()`
- `RegexHelper::getThematicBreakRegex()`
- Removed the second `$preserveEntities` parameter from `Xml:escape()`
## [0.18.5] - 2019-03-28
### Fixed
- Fixed the adjoining `Text` collapser not handling the full tree (thephpleague/commonmark-ext-autolink#10)
## [0.18.4] - 2019-03-23
### Changed
- Modified how URL normalization decodes certain characters in order to align with the JS library's output
- Disallowed unescaped `(` in parenthesized link title
### Fixed
- Fixed two exponential backtracking issues
## [0.18.3] - 2019-03-21
This is a **security update** release.
### Changed
- XML/HTML entities in attributes will no longer be preserved when rendering (#353)
### Fixed
- Fix XSS vulnerability caused by improper preservation of entities when rendering (#353)
### Deprecated
- Deprecated the `$preserveEntites` argument of `Xml::escape()` for removal in the next release (#353)
## [0.18.2] - 2019-03-16
### Fixed
- Fixed adjoining `Text` elements not being collapsed after delimiter processing
### Deprecated
- Deprecated the `CommonmarkConverter::VERSION` constant for removal in 1.0.0
## [0.18.1] - 2018-12-29
This is a **security update** release.
### Fixed
- Fix XSS vulnerability caused by URL normalization not handling/encoding newlines properly (#337, CVE-2018-20583)
## [0.18.0] - 2018-09-18
### Added
- Added `ConverterInterface` to `Converter` and `CommonMarkConverter` (#330)
- Added `ListItem::getListData()` method (#329)
### Changed
- Links with `target="_blank"` will also get `rel="noopener noreferrer"` by default (#331)
- Implemented several performance optimizations (#324)
## [0.17.5] - 2018-03-29
### Fixed
- Fixed incorrect version constant value (again)
- Fixed release checklist to prevent the above from happening
- Fixed incorrect dates in CHANGELOG
## [0.17.4] - 2018-03-28
### Added
- Added `ListBlock::setTight()` method
## [0.17.3] - 2018-03-26
### Fixed
- Fixed incorrect version constant value
## [0.17.2] - 2018-03-25
### Added
- Added new `RegexHelper::isEscapable()` method
### Fixed
- Fixed spec compliance bug where escaped spaces should not be allowed in link destinations
## [0.17.1] - 2018-03-18
### Added
- Added a new constant containing the current version: `CommonMarkConverter::VERSION` (#314)
## [0.17.0] - 2017-12-30
This release contains several breaking changes and a minimum PHP version bump - see <UPGRADE.md> for more details.
### Added
- Added new `max_nesting_level` setting (#243)
- Added minor performance optimizations to `Cursor`
### Changed
- Minimum PHP version is now 5.6.5.
- All full and partial regular expressions in `RegexHelper` are now defined as constants instead of being built on-the-fly.
- `Cursor::saveState()` now returns an `array` instead of a `CursorState` object.
- `Cursor::restoreState()` now accepts an `array` parameter instead of a `CursorState` object.
- Saving/restoring the Cursor state no longer tracks things that don't change (like the text content).
- `RegexHelper` is now `final`.
- References to `InlineContainer` changed to new `InlineContainerInterface` interface.
- `MiscExtension::addInlineParser()` and `MiscExtension::addBlockRenderer()` now return `$this` instead of nothing.
### Fixed
- Fixed `Reference::normalizeReference()` not properly collapsing whitespace to a single space
### Deprecated
- `RegexHelper::getInstance()` and all instance (non-static) methods have been deprecated.
- The `InlineContainer` interface has been deprecated. Use `InlineContainerInterface` instead.
### Removed
- Removed support for PHP 5.4 and 5.5.
- Removed `CursorState` class
- Removed all previous deprecations:
- `Cursor::getFirstNonSpacePosition()`
- `Cursor::getFirstNonSpaceCharacter()`
- `Cursor::advanceWhileMatches()`
- `Cursor::advanceToFirstNonSpace()`
- `ElementRendererInterface::escape()`
- `HtmlRenderer::escape()`
- `RegexHelper::REGEX_UNICODE_WHITESPACE`
- `RegexHelper::getLinkDestinationRegex()`
## [0.16.0] - 2017-10-30
This release contains breaking changes, several performance improvements, and two deprecations:
### Added
- Added new `Xml` utility class; moved HTML/XML escaping logic into there (see deprecations below)
### Changed
- `Environment::getInlineParsersForCharacter()` now returns an empty array (instead of `null`) when no matching parsers are found
- Three utility classes are now marked `final`:
- `Html5Entities`
- `LinkParserHelper`
- `UrlEncoder`
### Fixed
- Improved performance of several methods (for a 10% overall performance boost - #292)
### Deprecated
The following methods were deprecated and are scheduled for removal in 0.17.0 or 1.0.0 (whichever comes first). See <UPGRADE.md> for more information.
- `Cursor::advanceWhileMatches()` deprecated; use `Cursor::match()` instead.
- `HtmlRenderer::escape()` deprecated; use `Xml::escape()` instead.
### Removed
- Removed `DelimiterStack::findFirstMatchingOpener()` which was previously deprecated in 0.15.0
## [0.15.7] - 2017-10-26
### Fixed
- Improved performance of `Cursor::advanceBy()` (for a 16% performance boost)
## [0.15.6] - 2017-08-08
### Fixed
- Fixed URI normalization not properly encoding/decoding special characters in certain cases (#287)
## [0.15.5] - 2017-08-05
This release bumps spec compliance to 0.28 without breaking changes to the API.
### Added
- Project is now tested against PHP 7.2
### Changed
- Bumped CommonMark spec target to 0.28
- Changed internal implementation of `LinkParserHelper::parseLinkDestination()` to allow nested parens
- Changed precedence of strong/emph when both nestings are possible (rule 14)
- Allow tabs before and after ATX closing header
### Fixed
- Fixed HTML type 6 block regex matching against `<pre>` (it shouldn't) and not matching `<iframe>` (it should)
- Fixed reference parser incorrectly handling escaped `]` characters
- Fixed "multiple of 3" delimiter run calculations
### Deprecated
An unused constant and static method were deprecated and will be removed in a future release. See <UPGRADE.md> for more information.
- Deprecated `RegexHelper::REGEX_UNICODE_WHITESPACE` (no longer used)
- Deprecated `RegexHelper::getLinkDestinationRegex()` (no longer used)
## [0.15.4] - 2017-05-09
### Added
- Added new methods to `Cursor` (#280):
- `advanceToNextNonSpaceOrNewline()` - Identical replacement for the (now-deprecated) `advanceToFirstNonSpace()` method
- `advanceToNextNonSpaceOrTab()` - Similar replacement for `advanceToFirstNonSpace()` but with proper tab handling
- `getNextNonSpaceCharacter()` - Identical replacement for the (now-deprecated) `getFirstNonSpaceCharacter()` method
- `getNextNonSpacePosition()` - Identical replacement for the (now-deprecated) `getFirstNonSpacePosition()` method
- Added new method to `CursorState` (#280):
- `getNextNonSpaceCache()` - Identical replacement for the (now-deprecated) `getFirstNonSpaceCache()` method
### Fixed
- Fixed duplicate characters in non-intended lines containing tabs (#279)
### Deprecated
**All deprecations listed here will be removed in a future 0.x release.** See [UPGRADE.md](UPGRADE.md) for instructions on preparing your code for the eventual removal of these methods.
- Deprecated `Cursor::advanceToFirstNonSpace()` (#280)
- Use `advanceToNextNonSpaceOrTab()` or `advanceToNextNonSpaceOrNewline()` instead, depending on your requirements
- Deprecated `Cursor::getFirstNonSpaceCharacter()` (#280)
- Use `Cursor::getNextNonSpaceCharacter()` instead
- Deprecated `Cursor::getFirstNonSpacePosition()` (#280)
- Use `Cursor::getNextNonSpacePosition()` instead
- Deprecated `CursorState::getFirstNonSpaceCache()` (#280)
- Use `CursorState::getNextNonSpaceCache()` instead
## [0.15.3] - 2016-12-18
### Fixed
- Allow inline parsers matching regex delimiter to be created (#271, #272)
## [0.15.2] - 2016-11-22
### Changed
- Bumped spec target version to 0.27 (#268)
- H2-H6 elements are now parsed as HTML block elements instead of HTML inlines
### Fixed
- Fixed incomplete punctuation regex
- Fixed shortcut links not being allowed before a `(`
- Fixed distinction between Unicode whitespace and regular whitespace
## [0.15.1] - 2016-11-08
### Fixed
- Fixed setext heading underlines not allowing trailing tabs (#266)
## [0.15.0] - 2016-09-14
### Added
- Added preliminary support for PHP 7.1 (#259)
- Added more regression tests (#258, #260)
### Changed
- Bumped spec target version to 0.26 (#260)
- The `CursorState` constructor requires an additional parameter (#258)
- Ordered lists cannot interupt a paragraph unless they start with `1` (#260)
- Blank list items cannot interupt a paragraph (#260)
### Deprecated
- Deprecated `DelimiterStack::findFirstMatchingOpener()` - use `findMatchingOpener()` instead (#260)
### Fixed
- Fixed tabs in ATX headers and thematic breaks (#260)
- Fixed issue where cursor state was not being restored properly (#258, #260)
- This fixed the lists-with-tabs regression reported in #258
### Removed
- Removed an unnecessary check in `Cursor::advanceBy()` (#260)
- Removed the two-blanks-break-out-of-lists feature (#260)
## [0.14.0] - 2016-07-02
### Added
- The `safe` option is deprecated and replaced by 2 new options (#253, #255):
- `html_input` (`strip`, `allow` or `escape`): how to handle untrusted HTML input (the default is `strip` for BC reasons)
- `allow_unsafe_links` (`true` or `false`): whether to allow risky image URLs and links (the default is `true` for BC reasons)
### Deprecated
- The `safe` option is now deprecated and will be removed in the 1.0.0 release.
## [0.13.4] - 2016-06-14
### Fixed
- Fixed path to `autoload.php` within bin/commonmark (#250)
## [0.13.3] - 2016-05-21
### Added
- Added `setUrl()` method for `Link` and `Image` elements (#227, #244)
- Added cebe/markdown to the benchmark tool (#245)
## [0.13.2] - 2016-03-27
### Added
- Added ability to invoke `Converter` as a function (#233, #239)
- Added new `advanceBySpaceOrTab` convenience method to `Cursor`
### Changed
- Bumped spec target version to 0.25
- Adjusted how tabs are handled by the `Cursor` (#234)
- Made a couple small micro-optimizations to heavily used functions (#240)
- Updated URLs in docblocks to use HTTPS where possible (#238)
## [0.13.1] - 2016-03-09
### Changed
- Refactored `EmphasisParser::parse()` to simplify it (#223)
- Updated dev dependencies (#218 & #220)
### Fixed
- Fixed invalid regex generated when no inline parsers are defined (#224)
- Fixed logic bug with blank line after empty list item (#230)
- Fixed some incorrect code comments
### Removed
- Removed unused variables (#223)
## [0.13.0] - 2016-01-13
### Added
- Added AST document processors (#210)
- Added optional `Environment` parameter to `CommonMarkConverter` constructor
### Changed
- Renamed "header" things to "heading" for spec consistency
- `Header` => `Heading`
- `ATXHeaderParser` => `ATXHeadingParser`
- `SetExtHeaderParser` => `SetExtHeadingParser`
- `HeaderRenderer` => `HeadingRenderer`
- Renamed "HorizontalRule" to "ThematicBreak" for spec consistency
- `HorizontalRule` => `ThematicBreak`
- `HorizontalRuleParser` => `ThematicBreakParser`
- `HorizontalRuleRenderer` => `ThematicBreakRenderer`
- `HorizontalRuleRendererTest` => `ThematicBreakRendererTest`
- `RegexHelper::getHRuleRegex()` => `RegexHelper::getThematicBreakRegex()`
- Renamed inline "Html" and "RawHtml" to "HtmlInline" for consistency
- `Html` => `HtmlInline`
- `RawHtmlParser` => `HtmlInlineParser`
- `RawHtmlRenderer` => `HtmlInlineRenderer`
- Don't allow whitespace between link text and link label of a reference link (spec change)
- Don't allow spaces in link destinations, even in `<>`
- Allow multiline setext header content
- The `Heading` constructor now allows `$contents` to be a `string` (old behavior) or `string[]` (new)
### Fixed
- Fixed several list issues and regressions (jgm/commonmark.js#59)
### Removed
- Removed schema whitelist from autolink regex
- Moved SmartPunct functionality into new [league/commonmark-extras](https://github.com/thephpleague/commonmark-extras) package
## [0.12.0] - 2015-11-04
### Added
- Added ability to configure characters and disable emphasis/strong (#135)
- Added new ConfigurationAwareInterface support for all parsers, processors, and renderers (#201)
- Added HTML safe mode to handle untrusted input (#200, #201)
- Safe mode is disabled by default for backwards-compatibility
- To enable it, set the `safe` option to `true`
- Added AppVeyor integration for automated unit/functional testing on Windows (#195)
### Changed
- `AbstractBlock::finalize()` now requires a second parameter, `$endLineNumber`
- `RegexHelper::REGEX_ENTITY` no longer includes the starting `/` or the ending `/i` (#194)
- `Node::setParent()` now accepts null values (#203)
### Fixed
- Fixed incorrect `endLine` positions (#187)
- Fixed `DocParser::preProcessInput` dropping up to 2 ending newlines instead of just one
- Fixed `EntityParser` not checking for ampersands at the start of the current position (#192, #194)
### Removed
- Removed protected function Context::addChild()
- It was a duplicate of the Context::addBlock() method
- Disabled STDIN reading on `bin/commonmark` for Windows due to PHP issues (#189, #195)
## [0.11.3] - 2015-09-25
### Fixed
- Reset container after closing containing lists (#183; jgm/commonmark.js#67)
- The temporary fix from 0.11.2 was reverted
## [0.11.2] - 2015-09-23
### Fixed
- Fixed parser checking acceptsLines on the wrong element (#183)
## [0.11.1] - 2015-09-22
### Changed
- Tightened up some loose comparisons
### Fixed
- Fixed missing "bin" directive in composer.json
- Updated a docblock to match recent changes to method parameters
### Removed
- Removed unused variable from within QuoteProcessor's closure
## [0.11.0] - 2015-09-19
### Added
- Added new `Node` class, which both `AbstractBlock` and `AbstractInline` extend from (#169)
- Added a `NodeWalker` and `NodeWalkerEvent` to traverse the AST without using recursion
- Added new `InlineContainer` interface for blocks
- Added new `getContainer()` and `getReferenceMap()` methods to `InlineParserContext`
- Added `iframe` to whitelist of HTML block tags (as per spec)
- Added `./bin/commonmark` for converting Markdown at the command line
### Changed
- Bumped spec target version to 0.22
- Revised AST to use a double-linked list (#169)
- `AbstractBlock` and `AbstractInline` both extend from `Node`
- Sub-classes must implement new `isContainer()` method
- Other major changes to `AbstractBlock`:
- `getParent()` is now `parent()`
- `setParent()` now expects a `Node` instead of an `AbstractBlock`
- `getChildren()` is now `children()`
- `getLastChild()` is now `lastChild()`
- `addChild()` is now `appendChild()`
- `InlineParserContext` is constructed using the container `AbstractBlock` and the document's `RefereceMap`
- The constructor will automatically create the `Cursor` using the container's string contents
- `InlineParserEngine::parse` now requires the `Node` container and the document's `ReferenceMap` instead of a `ContextInterface` and `Cursor`
- Changed `Delimiter` to reference the actual inline `Node` instead of the position
- The `int $pos` protected member and constructor arg is now `Node $node`
- Use `getInlineNode()` and `setInlineNode()` instead of `getPos()` and `setPos()`
- Changed `DocParser::processInlines` to use a `NodeWalker` to iterate through inlines
- Walker passed as second argument instead of `AbstractBlock`
- Uses a `while` loop instead of recursion to traverse the AST
- `Image` and `Link` now only accept a string as their second argument
- Refactored how `CloseBracketParser::parse()` works internally
- `CloseBracketParser::createInline` no longer accepts label inlines
- Disallow list item starting with multiple blank lines (see jgm/CommonMark#332)
- Modified `AbstractBlock::setLastLineBlank()`
- Functionality moved to `AbstractBlock::shouldLastLineBeBlank()` and new `DocParser::setAndPropagateLastLineBlank()` method
- `AbstractBlock::setLastLineBlank()` is now a setter method for `AbstractBlock::$lastLineBlank`
- `AbstractBlock::handleRemainingContents()` is no longer abstract
- A default implementation is provided
- Removed duplicate code from sub-classes which used the default implementation - they'll just use the parent method from now on
### Fixed
- Fixed logic error in calculation of offset (see jgm/commonmark.js@94053a8)
- Fixed bug where `DocParser` checked the wrong method to determine remainder handling behavior
- Fixed bug where `HorizontalRuleParser` failed to advance the cursor beyond the parsed horizontal rule characters
- Fixed `DocParser` not ignoring the final newline of the input (like the reference parser does)
### Removed
- Removed `Block\Element\AbstractInlineContainer`
- Extend `AbstractBlock` and implement `InlineContainer` instead
- Use child methods instead of `getInlines` and `setInlines`
- Removed `AbstractBlock::replaceChild()`
- Call `Node::replaceWith()` directly the child node instead
- Removed the `getInlines()` method from `InlineParserContext`
- Add parsed inlines using `$inlineContext->getContainer()->appendChild()` instead of `$inlineContext->getInlines()->add()`
- Removed the `ContextInterface` argument from `AbstractInlineParser::parse()` and `InlineParserEngine::parseCharacter`
- Removed the first `ArrayCollection $inlines` argument from `InlineProcessorInterface::processInlines()`
- Removed `CloseBracketParser::nullify()`
- Removed `pre` from rule 6 of HTML blocks (see jgm/CommonMark#355)
## [0.10.0] - 2015-07-25
### Added
- Added parent references to inline elements (#124)
- Added smart punctuation extension (#134)
- Added HTML block types
- Added indentation caching to the cursor
- Added automated code style checks (#133)
- Added support for tag attributes in renderers (#101, #165)
### Changed
- Bumped spec target version to 0.21
- Revised HTML block parsing to conform to new spec (jgm/commonmark.js@99bd473)
- Imposed 9-digit limit on ordered list markers, per spec
- Allow non-initial hyphens in html tag names (jgm/CommonMark#239)
- Updated list of block tag names
- Changed tab/indentation handling to meet the new spec behavior
- Modified spec tests to show spaces and tabs in test results
- Replaced `HtmlRendererInterface` with `ElementRendererInterface` (#141)
- Removed the unnecessary `trim()` and string cast from `ListItemRenderer`
### Fixed
- Fixed link reference definition edge case (#120)
- Allow literal (non-escaping) backslashes in link destinations (#118)
- Allow backslash-escaped backslashes in link labels (#119)
- Allow link labels up to 999 characters (per the spec)
- Properly split on whitespace when determining code block class (jgm/commonmark.js#54)
- Fixed code style issues (#132, #133, #151, #152)
- Fixed wording for invalid inline exception (#136)
### Removed
- Removed the advance-by-one optimization due to added cursor complexity
## [0.9.0] - 2015-06-18
### Added
- Added public $data array to block elements (#95)
- Added `isIndented` helper method to `Cursor`
- Added a new `Converter` base class which `CommonMarkConverter` extends from (#105)
### Changed
- Bumped spec target version to 0.20 (#112)
- Renamed ListBlock::$data and ListItem::$data to $listData
- Require link labels to contain non-whitespace (jgm/CommonMark#322)
- Use U+FFFD for entities resolving to 0 (jgm/CommonMark#323)
- Moved `IndentedCodeParser::CODE_INDENT_LEVEL` to `Cursor::INDENT_LEVEL`
- Changed arrays to short syntax (#116)
- Improved efficiency of DelimiterStack iteration (jgm/commonmark.js#43)
### Fixed
- Fixed open block tag followed by newline not being recognized (jgm/CommonMark#324)
- Fixed indented lists sometimes being parsed incorrectly (jgm/commonmark.js#42)
## [0.8.0] - 2015-04-29
### Added
- Allow swapping built-in renderers without using their fully qualified names (#84)
- Lots of unit tests (for existing code)
- Ability to include arbitrary functional tests in addition to spec-based tests
### Changed
- Dropped support for PHP 5.3 (#64 and #76)
- Bumped spec target version to 0.19
- Made the AbstractInlineContainer be abstract
- Moved environment config. logic into separate class
### Fixed
- Fixed underscore emphasis to conform to spec changes (jgm/CommonMark#317)
### Removed
- Removed PHP 5.3 workaround (see commit 5747822)
- Removed unused AbstractWebResource::setUrl() method
- Removed unnecessary check for hrule when parsing lists (#85)
## [0.7.2] - 2015-03-08
### Changed
- Bumped spec target version to 0.18
### Fixed
- Fixed broken parsing of emphasized text ending with a '0' character (#81)
## [0.7.1] - 2015-03-01
### Added
- All references can now be obtained from the `ReferenceMap` via `listReferences()` (#73)
- Test against PHP 7.0 (nightly) but allow failures
### Changed
- ListData::$start now defaults to null instead of 0 (#74)
- Replace references to HtmlRenderer with new HtmlRendererInterface
### Fixed
- Fixed 0-based ordered lists starting at 1 instead of 0 (#74)
- Fixed errors parsing multi-byte characters (#78 and #79)
## [0.7.0] - 2015-02-16
### Added
- More unit tests to increase code coverage
### Changed
- Enabled the InlineParserEngine to parse several non-special characters at once (performance boost)
- NewlineParser no longer attempts to parse spaces; look-behind is used instead (major performance boost)
- Moved closeUnmatchedBlocks into its own class
- Image and link elements now extend AbstractInlineContainer; label data is stored via $inlineContents instead
- Renamed AbstractInlineContainer::$inlineContents and its getter/setter
### Removed
- Removed the InlineCollection class
- Removed the unused ArrayCollection::splice() method
- Removed impossible-to-reach code in Cursor::advanceToFirstNonSpace
- Removed unnecessary test from the InlineParserEngine
- Removed unnecessary/unused RegexHelper::getMainRegex() method
## [0.6.1] - 2015-01-25
### Changed
- Bumped spec target version to 0.17
- Updated emphasis parsing for underscores to prevent intra-word emphasis
- Deferred closing of fenced code blocks
## [0.6.0] - 2015-01-09
### Added
- Bulk registration of parsers/renderers via extensions (#45)
- Proper UTF-8 support, especially in the Cursor; mbstring extension is now required (#49)
- Environment is now configurable; options can be accessed in its parsers/renderers (#56)
- Added some unit tests
### Changed
- Bumped spec target version to 0.15 (#50)
- Parsers/renderers are now lazy-initialized (#52)
- Some private elements are now protected for easier extending, especially on Element classes (#53)
- Renderer option keys changed from camelCase to underscore_case (#56)
- Moved CommonMark parser/render definitions into CommonMarkCoreExtension
### Fixed
- Improved parsing of emphasis around punctuation
- Improved regexes for CDATA and HTML comments
- Fixed issue with HTML content that is considered false in loose comparisons, like `'0'` (#55)
- Fixed DocParser trying to add empty strings to closed containers (#58)
- Fixed incorrect use of a null parameter value in the HtmlElementTest
### Removed
- Removed unused ReferenceDefinition* classes (#51)
- Removed UnicodeCaseFolder in favor of mb_strtoupper
## [0.5.1] - 2014-12-27
### Fixed
- Fixed infinite loop and link-in-link-in-image parsing (#37)
### Removed
- Removed hard dependency on mbstring extension; workaround used if not installed (#38)
## [0.5.0] - 2014-12-24
### Added
- Support for custom directives, parsers, and renderers
### Changed
- Major refactoring to de-couple directives from the parser, support custom directive functionality, and reduce complexity
- Updated references to stmd.js in README and docblocks
- Modified CHANGELOG formatting
- Improved travis configuration
- Put tests in autoload-dev
### Fixed
- Fixed CommonMarkConverter re-creating object each time new text is converted (#26)
### Removed
- Removed HtmlRenderer::render() (use the renderBlock method instead)
- Removed dependency on symfony/options-resolver (fixes #20)
## [0.4.0] - 2014-12-15
### Added
- Added some missing copyright info
### Changed
- Changed namespace to League\CommonMark
- Made compatible with spec version 0.13
- Moved delimiter stack functionality into separate class
### Fixed
- Fixed regex which caused HHVM tests to fail
## [0.3.0] - 2014-11-28
### Added
- Made renderer options configurable (issue #7)
### Changed
- Made compatible with spec version 0.12
- Stack-based parsing now used for emphasis, links and images
- Protected some of the internal renderer methods which shouldn't have been `public`
- Minor code clean-up (including PSR-2 compliance)
### Removed
- Removed unnecessary distinction between ATX and Setext headers
## [0.2.1] - 2014-11-09
### Added
- Added simpler string replacement to a method
### Changed
- Removed "is" prefix from boolean methods
* Updated to latest version of PHPUnit
* Target specific spec version
## [0.2.0] - 2014-11-09
### Changed
- Mirrored significant changes and improvements from stmd.js
- Made compatible with spec version 0.10
- Updated location of JGM's repository
- Allowed HHVM tests to fail without affecting overall build success
### Removed
- Removed composer.lock
- Removed fixed reference to jgm/stmd@0275f34
## [0.1.2] - 2014-09-28
### Added
- Added performance benchmarking tool (issue #2)
- Added more badges to the README
### Changed
- Fix JS -> PHP null judgement (issue #4)
- Updated phpunit dependency
## [0.1.1] - 2014-09-08
### Added
- Add anchors to regexes
### Changed
- Updated target spec (now compatible with jgm/stmd:spec.txt @ 2cf0750)
- Adjust HTML output for fenced code
- Adjust block-level tag regex (remove "br", add "iframe")
- Fix incorrect handling of nested emphasis
## 0.1.0
### Added
- Initial commit (compatible with jgm/stmd:spec.txt @ 0275f34)
[0.19.3]: https://github.com/thephpleague/commonmark/compare/0.19.2...0.19.3
[0.19.2]: https://github.com/thephpleague/commonmark/compare/0.19.1...0.19.2
[0.19.1]: https://github.com/thephpleague/commonmark/compare/0.19.0...0.19.1
[0.19.0]: https://github.com/thephpleague/commonmark/compare/0.18.5...0.19.0
[0.18.5]: https://github.com/thephpleague/commonmark/compare/0.18.4...0.18.5
[0.18.4]: https://github.com/thephpleague/commonmark/compare/0.18.3...0.18.4
[0.18.3]: https://github.com/thephpleague/commonmark/compare/0.18.2...0.18.3
[0.18.2]: https://github.com/thephpleague/commonmark/compare/0.18.1...0.18.2
[0.18.1]: https://github.com/thephpleague/commonmark/compare/0.18.0...0.18.1
[0.18.0]: https://github.com/thephpleague/commonmark/compare/0.17.5...0.18.0
[0.17.5]: https://github.com/thephpleague/commonmark/compare/0.17.4...0.17.5
[0.17.4]: https://github.com/thephpleague/commonmark/compare/0.17.3...0.17.4
[0.17.3]: https://github.com/thephpleague/commonmark/compare/0.17.2...0.17.3
[0.17.2]: https://github.com/thephpleague/commonmark/compare/0.17.1...0.17.2
[0.17.1]: https://github.com/thephpleague/commonmark/compare/0.17.0...0.17.1
[0.17.0]: https://github.com/thephpleague/commonmark/compare/0.16.0...0.17.0
[0.16.0]: https://github.com/thephpleague/commonmark/compare/0.15.7...0.16.0
[0.15.7]: https://github.com/thephpleague/commonmark/compare/0.15.6...0.15.7
[0.15.6]: https://github.com/thephpleague/commonmark/compare/0.15.5...0.15.6
[0.15.5]: https://github.com/thephpleague/commonmark/compare/0.15.4...0.15.5
[0.15.4]: https://github.com/thephpleague/commonmark/compare/0.15.3...0.15.4
[0.15.3]: https://github.com/thephpleague/commonmark/compare/0.15.2...0.15.3
[0.15.2]: https://github.com/thephpleague/commonmark/compare/0.15.1...0.15.2
[0.15.1]: https://github.com/thephpleague/commonmark/compare/0.15.0...0.15.1
[0.15.0]: https://github.com/thephpleague/commonmark/compare/0.14.0...0.15.0
[0.14.0]: https://github.com/thephpleague/commonmark/compare/0.13.4...0.14.0
[0.13.4]: https://github.com/thephpleague/commonmark/compare/0.13.3...0.13.4
[0.13.3]: https://github.com/thephpleague/commonmark/compare/0.13.2...0.13.3
[0.13.2]: https://github.com/thephpleague/commonmark/compare/0.13.1...0.13.2
[0.13.1]: https://github.com/thephpleague/commonmark/compare/0.13.0...0.13.1
[0.13.0]: https://github.com/thephpleague/commonmark/compare/0.12.0...0.13.0
[0.12.0]: https://github.com/thephpleague/commonmark/compare/0.11.3...0.12.0
[0.11.3]: https://github.com/thephpleague/commonmark/compare/0.11.2...0.11.3
[0.11.2]: https://github.com/thephpleague/commonmark/compare/0.11.1...0.11.2
[0.11.1]: https://github.com/thephpleague/commonmark/compare/0.11.0...0.11.1
[0.11.0]: https://github.com/thephpleague/commonmark/compare/0.10.0...0.11.0
[0.10.0]: https://github.com/thephpleague/commonmark/compare/0.9.0...0.10.0
[0.9.0]: https://github.com/thephpleague/commonmark/compare/0.8.0...0.9.0
[0.8.0]: https://github.com/thephpleague/commonmark/compare/0.7.2...0.8.0
[0.7.2]: https://github.com/thephpleague/commonmark/compare/0.7.1...0.7.2
[0.7.1]: https://github.com/thephpleague/commonmark/compare/0.7.0...0.7.1
[0.7.0]: https://github.com/thephpleague/commonmark/compare/0.6.1...0.7.0
[0.6.1]: https://github.com/thephpleague/commonmark/compare/0.6.0...0.6.1
[0.6.0]: https://github.com/thephpleague/commonmark/compare/0.5.1...0.6.0
[0.5.1]: https://github.com/thephpleague/commonmark/compare/0.5.0...0.5.1
[0.5.0]: https://github.com/thephpleague/commonmark/compare/0.4.0...0.5.0
[0.4.0]: https://github.com/thephpleague/commonmark/compare/0.3.0...0.4.0
[0.3.0]: https://github.com/thephpleague/commonmark/compare/0.2.1...0.3.0
[0.2.1]: https://github.com/thephpleague/commonmark/compare/0.2.0...0.2.1
[0.2.0]: https://github.com/thephpleague/commonmark/compare/0.1.2...0.2.0
[0.1.2]: https://github.com/thephpleague/commonmark/compare/0.1.1...0.1.2
[0.1.1]: https://github.com/thephpleague/commonmark/compare/0.1.0...0.1.1

530
vendor/league/commonmark/CHANGELOG.md vendored Normal file
View File

@@ -0,0 +1,530 @@
# Change Log
All notable changes to this project will be documented in this file.
Updates should follow the [Keep a CHANGELOG](https://keepachangelog.com/) principles.
## [Unreleased][unreleased]
## [1.6.7] - 2022-01-13
### Changed
- Added `ReturnTypeWillChange` attribute to prevent PHP 8.1 deprecation warnings (#785)
- Coerced punctuation counts to integers to ensure floats are never used
## [1.6.6] - 2021-07-17
### Fixed
- Fixed Mentions inside of links creating nested links against the spec's rules (#688)
## [1.6.5] - 2021-06-26
### Changed
- Simplified checks for thematic breaks
### Fixed
- Fixed ExternalLinkProcessor not handling autolinks by adjusting its priority to -50 (#681)
## [1.6.4] - 2021-06-19
### Changed
- Optimized attribute parsing to avoid inspecting every space character (30% performance boost)
## [1.6.3] - 2021-06-19
### Fixed
- Fixed incorrect parsing of tilde-fenced code blocks with leading spaces (#676)
## [1.6.2] - 2021-05-12
### Fixed
- Fixed incorrect error level for deprecation notices
## [1.6.1] - 2021-05-08
### Fixed
- Fixed `HeadingPermalinkProcessor` skipping text contents from certain nodes (#615)
## [1.6.0] - 2021-05-01
### Added
- Added forward-compatibility for [configuration options which will be changing in 2.0](https://commonmark.thephpleague.com/1.6/upgrading/):
- `commonmark/enable_em` (currently `enable_em` in 1.x)
- `commonmark/enable_strong` (currently `enable_strong` in 1.x)
- `commonmark/use_asterisk` (currently `use_asterisk` in 1.x)
- `commonmark/use_underscore` (currently `use_underscore` in 1.x)
- `commonmark/unordered_list_markers` (currently `unordered_list_markers` in 1.x)
- `mentions/*/prefix` (currently `mentions/*/symbol` in 1.x)
- `mentions/*/pattern` (currently `mentions/*/regex` in 1.x)
- `max_nesting_level` (currently supports `int` and `float` values in 1.x; will only support `int` in 2.0)
- Added new `MarkdownConverter` class for creating converters with custom environments; this replaces the previously-deprecated `Converter` class
- Added new `RegexHelper::matchFirst()` method
- Added new `Configuration::exists()` method
### Changed
- The `max_nesting_level` option now defaults to `PHP_INT_MAX` instead of `INF`
### Deprecated
- Deprecated the [configuration options shown above](https://commonmark.thephpleague.com/1.6/upgrading/)
- Deprecated the ability to pass a custom `Environment` into the constructors of `CommonMarkConverter` and `GithubFlavoredMarkdownConverter`; use `MarkdownConverter` instead
- Deprecated `ConfigurableEnvironmentInterface::setConfig()`; use `mergeConfig()` instead
- Deprecated calling `ConfigurableEnvironmentInterface::mergeConfig()` without any parameters
- Deprecated calling `Configuration::get()` and `EnvironmentInterface::getConfig()` without any parameters
- Deprecated calling `Configuration::set()` without the second `$value` parameter
- Deprecated `RegexHelper::matchAll()`; use `RegexHelper::matchFirst()` instead
- Deprecated extending the `ArrayCollection` class; will be marked `final` in 2.0
### Fixed
- Fixed missing check for empty arrays being passed into the `unordered_list_markers` configuration option
## [1.5.8] - 2021-03-28
### Fixed
- Fixed Table of Contents not rendering heading inlines properly (#587, #588)
- Fixed parsing of tables within list items (#590)
## [1.5.7] - 2020-10-31
### Fixed
- Fixed mentions not being parsed when appearing after non-word characters (#582)
## [1.5.6] - 2020-10-17
### Changed
- Blocks added outside of the parsing context now have their start/end line numbers defaulted to 0 to avoid type errors (#579)
### Fixed
- Fixed replacement blocks not inheriting the start line number of the container they're replacing (#579)
- Fixed Table of Contents blocks not having correct start/end line numbers (#579)
## [1.5.5] - 2020-09-13
### Changed
- Bumped CommonMark spec compliance to 0.28.2
### Fixed
- Fixed `textarea` elements not being treated as a type 1 HTML block (like `script`, `style`, or `pre`)
- Fixed autolink processor not handling other unmatched trailing parentheses
## [1.5.4] - 2020-08-17
### Fixed
- Fixed footnote ID configuration not taking effect (#524, #530)
- Fixed heading permalink slugs not being unique (#531, #534)
## [1.5.3] - 2020-07-19
### Fixed
- Fixed regression of multi-byte inline parser characters not being matched
## [1.5.2] - 2020-07-19
### Changed
- Significantly improved performance of the inline parser regex
### Fixed
- Fixed parent class lookups for non-existent classes on PHP 8 (#517)
## [1.5.1] - 2020-06-27
### Fixed
- Fixed UTF-8 encoding not being checked in the `UrlEncoder` utility (#509) or the `Cursor`
## [1.5.0] - 2020-06-21
### Added
- Added new `AttributesExtension` based on <https://github.com/webuni/commonmark-attributes-extension> (#474)
- Added new `FootnoteExtension` based on <https://github.com/rezozero/commonmark-ext-footnotes> (#474)
- Added new `MentionExtension` to replace `InlineMentionParser` with more flexibility and customization
- Added the ability to render `TableOfContents` nodes anywhere in a document (given by a placeholder)
- Added the ability to properly clone `Node` objects
- Added options to customize the value of `rel` attributes set via the `ExternalLink` extension (#476)
- Added a new `heading_permalink/slug_normalizer` configuration option to allow custom slug generation (#460)
- Added a new `heading_permalink/symbol` configuration option to replace the now deprecated `heading_permalink/inner_contents` configuration option (#505)
- Added `SlugNormalizer` and `TextNormalizer` classes to make normalization reusable by extensions (#485)
- Added new classes:
- `TableOfContentsGenerator`
- `TableOfContentsGeneratorInterface`
- `TableOfContentsPlaceholder`
- `TableOfContentsPlaceholderParser`
- `TableOfContentsPlaceholderRenderer`
### Changed
- "Moved" the `TableOfContents` class into a new `Node` sub-namespace (with backward-compatibility)
- Reference labels are now generated and stored in lower-case instead of upper-case
- Reference labels are no longer normalized inside the `Reference`, only the `ReferenceMap`
### Fixed
- Fixed reference label case folding polyfill not being consistent between different PHP versions
### Deprecated
- Deprecated the `CommonMarkConverter::VERSION` constant (#496)
- Deprecated `League\CommonMark\Extension\Autolink\InlineMentionParser` (use `League\CommonMark\Extension\Mention\MentionParser` instead)
- Deprecated everything under `League\CommonMark\Extension\HeadingPermalink\Slug` (use the classes under `League\CommonMark\Normalizer` instead)
- Deprecated `League\CommonMark\Extension\TableOfContents\TableOfContents` (use the one in the new `Node` sub-namespace instead)
- Deprecated the `STYLE_` and `NORMALIZE_` constants in `TableOfContentsBuilder` (use the ones in `TableOfContentsGenerator` instead)
- Deprecated the `\League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkRenderer::DEFAULT_INNER_CONTENTS` constant (#505)
- Deprecated the `heading_permalink/inner_contents` configuration option in the `HeadingPermalink` extension (use the new `heading_permalink/symbol` configuration option instead) (#505)
## [1.4.3] - 2020-05-04
### Fixed
- Fixed certain Unicode letters, numbers, and marks not being preserved when generating URL slugs (#467)
## [1.4.2] - 2020-04-24
### Fixed
- Fixed inline code blocks not be included within heading permalinks (#457)
## [1.4.1] - 2020-04-20
### Fixed
- Fixed BC break caused by ConverterInterface alias not being used by some DI containers (#454)
## [1.4.0] - 2020-04-18
### Added
- Added new [Heading Permalink extension](https://commonmark.thephpleague.com/extensions/heading-permalinks/) (#420)
- Added new [Table of Contents extension](https://commonmark.thephpleague.com/extensions/table-of-contents/) (#441)
- Added new `MarkdownConverterInterface` as a long-term replacement for `ConverterInterface` (#439)
- Added new `DocumentPreParsedEvent` event (#427, #359, #399)
- Added new `ListBlock::TYPE_BULLET` constant as a replacement for `ListBlock::TYPE_UNORDERED`
- Added new `MarkdownInput` class and `MarkdownInputInterface` to handle pre-parsing and allow listeners to replace Markdown contents
### Changed
- Block & inline renderers will now render child classes automatically (#222, #209)
- The `ListBlock` constants now use fully-lowercased values instead of titlecased values
- Significantly improved typing
### Fixed
- Fixed loose comparison when checking for table alignment
- Fixed `StaggeredDelimiterProcessor` returning from a `void` function
### Deprecated
- The `Converter` class has been deprecated; use `CommonMarkConverter` instead (#438, #439)
- The `ConverterInterface` has been deprecated; use `MarkdownConverterInterface` instead (#438, #439)
- The `bin/commonmark` script has been deprecated
- The following methods of `ArrayCollection` have been deprecated:
- `add()`
- `set()`
- `get()`
- `remove()`
- `isEmpty()`
- `contains()`
- `indexOf()`
- `containsKey()`
- `replaceWith()`
- `removeGaps()`
- The `ListBlock::TYPE_UNORDERED` constant has been deprecated, use `ListBlock::TYPE_BULLET` instead
## [1.3.4] - 2020-04-13
### Fixed
- Fixed configuration/environment not being injected into event listeners when adding them via `[$instance, 'method']` callable syntax (#440)
## [1.3.3] - 2020-04-05
### Fixed
- Fixed event listeners not having the environment or configuration injected if they implemented the `EnvironmentAwareInterface` or `ConfigurationAwareInterface` (#423)
## [1.3.2] - 2020-03-25
### Fixed
- Optimized URL normalization in cases where URLs don't contain special characters (#417, #418)
## [1.3.1] - 2020-02-28
### Fixed
- Fixed return types of `Environment::createCommonMarkEnvironment()` and `Environment::createGFMEnvironment()`
## [1.3.0] - 2020-02-08
### Added
- Added (optional) **full GFM support!** 🎉🎉🎉 (#409)
- Added check to ensure Markdown input is valid UTF-8 (#401, #405)
- Added new `unordered_list_markers` configuration option (#408, #411)
### Changed
- Introduced several micro-optimizations for a 5-10% performance boost
## [1.2.2] - 2020-01-15
This release contains the same changes as 1.1.3:
### Fixed
- Fixed link parsing edge case (#403)
## [1.1.3] - 2020-01-15
### Fixed
- Fixed link parsing edge case (#403)
## [1.2.1] - 2020-01-14
### Changed
- Introduced several micro-optimizations, reducing the parse time by 8%
## [1.2.0] - 2020-01-09
### Changed
- Removed URL decoding step before encoding (more performant and better matches the JS library)
- Removed redundant token from HTML tag regex
## [1.1.2] - 2019-12-09
### Fixed
- Fixed URL normalization not handling non-UTF-8 sequences properly (#395, #396)
## [1.1.1] - 2019-11-11
### Fixed
- Fixed handling of link destinations with unbalanced unescaped parens
- Fixed adding delimiters to stack which can neither open nor close a run
## [1.1.0] - 2019-10-31
### Added
- Added a new `Html5EntityDecoder` class (#387)
### Changed
- Improved performance by 10% (#389)
- Made entity decoding less memory-intensive (#386, #387)
### Fixed
- Fixed PHP 7.4 compatibility issues
### Deprecated
- Deprecated the `Html5Entities` class - use `Html5EntityDecoder` instead (#387)
## [1.0.0] - 2019-06-29
No changes were made since 1.0.0-rc1.
## [1.0.0-rc1] - 2019-06-19
### Added
- Extracted a `ReferenceMapInterface` from the `ReferenceMap` class
- Added optional `ReferenceMapInterface` parameter to the `Document` constructor
### Changed
- Replaced all references to `ReferenceMap` with `ReferenceMapInterface`
- `ReferenceMap::addReference()` no longer returns `$this`
### Fixed
- Fixed bug where elements with content of `"0"` wouldn't be rendered (#376)
## [1.0.0-beta4] - 2019-06-05
### Added
- Added event dispatcher functionality (#359, #372)
### Removed
- Removed `DocumentProcessorInterface` functionality in favor of event dispatching (#373)
## [1.0.0-beta3] - 2019-05-27
### Changed
- Made the `Delimiter` class final and extracted a new `DelimiterInterface`
- Modified most external usages to use this new interface
- Renamed three `Delimiter` methods:
- `getOrigDelims()` renamed to `getOriginalLength()`
- `getNumDelims()` renamed to `getLength()`
- `setNumDelims()` renamed to `setLength()`
- Made additional classes final:
- `DelimiterStack`
- `ReferenceMap`
- `ReferenceParser`
- Moved `ReferenceParser` into the `Reference` sub-namespace
### Removed
- Removed unused `Delimiter` methods:
- `setCanOpen()`
- `setCanClose()`
- `setChar()`
- `setIndex()`
- `setInlineNode()`
- Removed fluent interface from `Delimiter` (setter methods now have no return values)
## [1.0.0-beta2] - 2019-05-27
### Changed
- `DelimiterProcessorInterface::process()` will accept any type of `AbstractStringContainer` now, not just `Text` nodes
- The `Delimiter` constructor, `getInlineNode()`, and `setInlineNode()` no longer accept generic `Node` elements - only `AbstractStringContainer`s
### Removed
- Removed all deprecated functionality:
- The `safe` option (use `html_input` and `allow_unsafe_links` options instead)
- All deprecated `RegexHelper` constants
- `DocParser::getEnvironment()` (you should obtain it some other way)
- `AbstractInlineContainer` (use `AbstractInline` instead and make `isContainer()` return `true`)
## [1.0.0-beta1] - 2019-05-26
### Added
- Added proper support for delimiters, including custom delimiters
- `addDelimiterProcessor()` added to `ConfigurableEnvironmentInterface` and `Environment`
- Basic delimiters no longer need custom parsers - they'll be parsed automatically
- Added new methods:
- `AdjacentTextMerger::mergeTextNodesBetweenExclusive()`
- `CommonMarkConveter::getEnvironment()`
- `Configuration::set()`
- Extracted some new interfaces from base classes:
- `DocParserInterface` created from `DocParser`
- `ConfigurationInterface` created from `Configuration`
- `ReferenceInterface` created from `Reference`
### Changed
- Renamed several methods of the `Configuration` class:
- `getConfig()` renamed to `get()`
- `mergeConfig()` renamed to `merge()`
- `setConfig()` renamed to `replace()`
- Changed `ConfigurationAwareInterface::setConfiguration()` to accept the new `ConfigurationInterface` instead of the concrete class
- Renamed the `AdjoiningTextCollapser` class to `AdjacentTextMerger`
- Replaced its `collapseTextNodes()` method with the new `mergeChildNodes()` method
- Made several classes `final`:
- `Configuration`
- `DocParser`
- `HtmlRenderer`
- `InlineParserEngine`
- `NodeWalker`
- `Reference`
- All of the block/inline parsers and renderers
- Reduced visibility of several internal methods to `private`:
- `DelimiterStack::findEarliest()`
- All `protected` methods in `InlineParserEngine`
- Marked some classes and methods as `@internal`
- `ElementRendererInterface` now requires a public `renderInline()` method; added this to `HtmlRenderer`
- Changed `InlineParserEngine::parse()` to require an `AbstractStringContainerBlock` instead of the generic `Node` class
- Un-deprecated the `CommonmarkConverter::VERSION` constant
- The `Converter` constructor now requires an instance of `DocParserInterface` instead of the concrete `DocParser`
- Changed `Emphasis`, `Strong`, and `AbstractWebResource` to directly extend `AbstractInline` instead of the (now-deprecated) intermediary `AbstractInlineContainer` class
### Fixed
- Fixed null errors when inserting sibling `Node`s without parents
- Fixed `NodeWalkerEvent` not requiring a `Node` via its constructor
- Fixed `Reference::normalizeReference()` improperly converting to uppercase instead of performing proper Unicode case-folding
- Fixed strong emphasis delimiters not being preserved when `enable_strong` is set to `false` (it now works identically to `enable_em`)
### Deprecated
- Deprecated `DocParser::getEnvironment()` (you should obtain it some other way)
- Deprecated `AbstractInlineContainer` (use `AbstractInline` instead and make `isContainer()` return `true`)
### Removed
- Removed inline processor functionality now that we have proper delimiter support:
- Removed `addInlineProcessor()` from `ConfigurableEnvironmentInterface` and `Environment`
- Removed `getInlineProcessors()` from `EnvironmentInterface` and `Environment`
- Removed `EmphasisProcessor`
- Removed `InlineProcessorInterface`
- Removed `EmphasisParser` now that we have proper delimiter support
- Removed support for non-UTF-8-compatible encodings
- Removed `getEncoding()` from `ContextInterface`
- Removed `getEncoding()`, `setEncoding()`, and `$encoding` from `Context`
- Removed `getEncoding()` and the second `$encoding` constructor param from `Cursor`
- Removed now-unused methods
- Removed `DelimiterStack::getTop()` (no replacement)
- Removed `DelimiterStack::iterateByCharacters()` (use the new `processDelimiters()` method instead)
- Removed the protected `DelimiterStack::findMatchingOpener()` method
[unreleased]: https://github.com/thephpleague/commonmark/compare/1.6.7...1.6
[1.6.7]: https://github.com/thephpleague/commonmark/compare/1.6.6...1.6.7
[1.6.6]: https://github.com/thephpleague/commonmark/compare/1.6.5...1.6.6
[1.6.5]: https://github.com/thephpleague/commonmark/compare/1.6.4...1.6.5
[1.6.4]: https://github.com/thephpleague/commonmark/compare/1.6.3...1.6.4
[1.6.3]: https://github.com/thephpleague/commonmark/compare/1.6.2...1.6.3
[1.6.2]: https://github.com/thephpleague/commonmark/compare/1.6.1...1.6.2
[1.6.1]: https://github.com/thephpleague/commonmark/compare/1.6.0...1.6.1
[1.6.0]: https://github.com/thephpleague/commonmark/compare/1.5.8...1.6.0
[1.5.8]: https://github.com/thephpleague/commonmark/compare/1.5.7...1.5.8
[1.5.7]: https://github.com/thephpleague/commonmark/compare/1.5.6...1.5.7
[1.5.6]: https://github.com/thephpleague/commonmark/compare/1.5.5...1.5.6
[1.5.5]: https://github.com/thephpleague/commonmark/compare/1.5.4...1.5.5
[1.5.4]: https://github.com/thephpleague/commonmark/compare/1.5.3...1.5.4
[1.5.3]: https://github.com/thephpleague/commonmark/compare/1.5.2...1.5.3
[1.5.2]: https://github.com/thephpleague/commonmark/compare/1.5.1...1.5.2
[1.5.1]: https://github.com/thephpleague/commonmark/compare/1.5.0...1.5.1
[1.5.0]: https://github.com/thephpleague/commonmark/compare/1.4.3...1.5.0
[1.4.3]: https://github.com/thephpleague/commonmark/compare/1.4.2...1.4.3
[1.4.2]: https://github.com/thephpleague/commonmark/compare/1.4.1...1.4.2
[1.4.1]: https://github.com/thephpleague/commonmark/compare/1.4.0...1.4.1
[1.4.0]: https://github.com/thephpleague/commonmark/compare/1.3.4...1.4.0
[1.3.4]: https://github.com/thephpleague/commonmark/compare/1.3.3...1.3.4
[1.3.3]: https://github.com/thephpleague/commonmark/compare/1.3.2...1.3.3
[1.3.2]: https://github.com/thephpleague/commonmark/compare/1.3.1...1.3.2
[1.3.1]: https://github.com/thephpleague/commonmark/compare/1.3.0...1.3.1
[1.3.0]: https://github.com/thephpleague/commonmark/compare/1.2.2...1.3.0
[1.2.2]: https://github.com/thephpleague/commonmark/compare/1.2.1...1.2.2
[1.2.1]: https://github.com/thephpleague/commonmark/compare/1.2.0...1.2.1
[1.2.0]: https://github.com/thephpleague/commonmark/compare/1.1.2...1.2.0
[1.1.3]: https://github.com/thephpleague/commonmark/compare/1.1.2...1.1.3
[1.1.2]: https://github.com/thephpleague/commonmark/compare/1.1.1...1.1.2
[1.1.1]: https://github.com/thephpleague/commonmark/compare/1.1.0...1.1.1
[1.1.0]: https://github.com/thephpleague/commonmark/compare/1.0.0...1.1.0
[1.0.0]: https://github.com/thephpleague/commonmark/compare/1.0.0-rc1...1.0.0
[1.0.0-rc1]: https://github.com/thephpleague/commonmark/compare/1.0.0-beta4...1.0.0-rc1
[1.0.0-beta4]: https://github.com/thephpleague/commonmark/compare/1.0.0-beta3...1.0.0-beta4
[1.0.0-beta3]: https://github.com/thephpleague/commonmark/compare/1.0.0-beta2...1.0.0-beta3
[1.0.0-beta2]: https://github.com/thephpleague/commonmark/compare/1.0.0-beta1...1.0.0-beta2
[1.0.0-beta1]: https://github.com/thephpleague/commonmark/compare/0.19.2...1.0.0-beta1

28
vendor/league/commonmark/LICENSE vendored Normal file
View File

@@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2014-2019, Colin O'Dell. All rights reserved. Based on commonmark.js,copyright (c) 2014-2018, John MacFarlane
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

217
vendor/league/commonmark/README.md vendored Normal file
View File

@@ -0,0 +1,217 @@
# league/commonmark
[![Latest Version](https://img.shields.io/packagist/v/league/commonmark.svg?style=flat-square)](https://packagist.org/packages/league/commonmark)
[![Total Downloads](https://img.shields.io/packagist/dt/league/commonmark.svg?style=flat-square)](https://packagist.org/packages/league/commonmark)
[![Software License](https://img.shields.io/badge/License-BSD--3-brightgreen.svg?style=flat-square)](LICENSE)
[![Build Status](https://img.shields.io/travis/thephpleague/commonmark/1.5.svg?style=flat-square)](https://travis-ci.org/thephpleague/commonmark)
[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/commonmark.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/commonmark/code-structure)
[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/commonmark.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/commonmark)
[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/126/badge)](https://bestpractices.coreinfrastructure.org/projects/126)
[![Sponsor development of this project](https://img.shields.io/badge/sponsor%20this%20package-%E2%9D%A4-ff69b4.svg?style=flat-square)](https://www.colinodell.com/sponsor)
![league/commonmark](commonmark-banner.png)
**league/commonmark** is a highly-extensible PHP Markdown parser created by [Colin O'Dell][@colinodell] which supports the full [CommonMark] spec and [Github-Flavored Markdown]. It is based on the [CommonMark JS reference implementation][commonmark.js] by [John MacFarlane] \([@jgm]\).
## 📦 Installation & Basic Usage
This project requires PHP 7.1 or higher with the `mbstring` extension. To install it via [Composer] simply run:
``` bash
$ composer require league/commonmark
```
The `CommonMarkConverter` class provides a simple wrapper for converting CommonMark to HTML:
```php
use League\CommonMark\CommonMarkConverter;
$converter = new CommonMarkConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
echo $converter->convertToHtml('# Hello World!');
// <h1>Hello World!</h1>
```
Or if you want Github-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead:
```php
use League\CommonMark\GithubFlavoredMarkdownConverter;
$converter = new GithubFlavoredMarkdownConverter([
'html_input' => 'strip',
'allow_unsafe_links' => false,
]);
echo $converter->convertToHtml('# Hello World!');
// <h1>Hello World!</h1>
```
Please note that only UTF-8 and ASCII encodings are supported. If your Markdown uses a different encoding please convert it to UTF-8 before running it through this library.
🔒 If you will be parsing untrusted input from users, please consider setting the `html_input` and `allow_unsafe_links` options per the example above. See <https://commonmark.thephpleague.com/security/> for more details. If you also do choose to allow raw HTML input from untrusted users, considering using a library (like [HTML Purifier](https://github.com/ezyang/htmlpurifier)) to provide additional HTML filtering.
## 📓 Documentation
Full documentation on advanced usage, configuration, and customization can be found at [commonmark.thephpleague.com][docs].
## ⏫ Upgrading
Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>.
## 💻 Github-Flavored Markdown
The `GithubFlavoredMarkdownConverter` shown earlier is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec:
- Autolinks
- Disallowed raw HTML
- Strikethrough
- Tables
- Task Lists
See the [Extensions documentation](https://commonmark.thephpleague.com/customization/extensions/) for more details on how to include only certain GFM features if you don't want them all.
## 🗃️ Related Packages
### Integrations
- [CakePHP 3](https://github.com/gourmet/common-mark)
- [Drupal](https://www.drupal.org/project/markdown)
- [Laravel 4 & 5](https://github.com/GrahamCampbell/Laravel-Markdown)
- [Sculpin](https://github.com/bcremer/sculpin-commonmark-bundle)
- [Symfony 2 & 3](https://github.com/webuni/commonmark-bundle)
- [Symfony 4](https://github.com/avensome/commonmark-bundle)
- [Twig Markdown extension](https://github.com/twigphp/markdown-extension)
- [Twig filter and tag](https://github.com/aptoma/twig-markdown)
### Included Extensions
See [our extension documentation](https://commonmark.thephpleague.com/extensions/overview) for a full list of extensions bundled with this library.
### Community Extensions
Custom parsers/renderers can be bundled into extensions which extend CommonMark. Here are some that you may find interesting:
- [Alt Three Emoji](https://github.com/AltThree/Emoji) An emoji parser for CommonMark.
- [Sup Sub extensions](https://github.com/OWS/commonmark-sup-sub-extensions) - Adds support of superscript and subscript (`<sup>` and `<sub>` HTML tags)
- [YouTube iframe extension](https://github.com/zoonru/commonmark-ext-youtube-iframe) - Replaces youtube link with iframe.
Others can be found on [Packagist under the `commonmark-extension` package type](https://packagist.org/packages/league/commonmark?type=commonmark-extension).
If you build your own, feel free to submit a PR to add it to this list!
### Others
Check out the other cool things people are doing with `league/commonmark`: <https://packagist.org/packages/league/commonmark/dependents>
## 🏷️ Versioning
[SemVer](http://semver.org/) is followed closely. Minor and patch releases should not introduce breaking changes to the codebase; however, they might change the resulting AST or HTML output of parsed Markdown (due to bug fixes, spec changes, etc.) As a result, you might get slightly different HTML, but any custom code built onto this library should still function correctly.
Any classes or methods marked `@internal` are not intended for use outside of this library and are subject to breaking changes at any time, so please avoid using them.
## 🛠️ Maintenance & Support
When a new **minor** version (e.g. `1.4` -> `1.5`) is released, the previous one (`1.4`) will continue to receive security and critical bug fixes for *at least* 3 months.
When a new **major** version is released (e.g. `1.5` -> `2.0`), the previous one (`1.5`) will receive critical bug fixes for *at least* 3 months and security updates for 6 months after that new release comes out.
(This policy may change in the future and exceptions may be made on a case-by-case basis.)
**Professional support, including notification of new releases and security updates, is available through a [Tidelift Subscription](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme).**
## 👷‍♀️ Contributing
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure with us.
If you encounter a bug in the spec, please report it to the [CommonMark] project. Any resulting fix will eventually be implemented in this project as well.
Contributions to this library are **welcome**, especially ones that:
* Improve usability or flexibility without compromising our ability to adhere to the [CommonMark spec]
* Mirror fixes made to the [reference implementation][commonmark.js]
* Optimize performance
* Fix issues with adhering to the [CommonMark spec]
Major refactoring to core parsing logic should be avoided if possible so that we can easily follow updates made to [the reference implementation][commonmark.js]. That being said, we will absolutely consider changes which don't deviate too far from the reference spec or which are favored by other popular CommonMark implementations.
Please see [CONTRIBUTING](https://github.com/thephpleague/commonmark/blob/latest/.github/CONTRIBUTING.md) for additional details.
## 🧪 Testing
``` bash
$ composer test
```
This will also test league/commonmark against the latest supported spec.
## 🚀 Performance Benchmarks
You can compare the performance of **league/commonmark** to other popular parsers by running the included benchmark tool:
``` bash
$ ./tests/benchmark/benchmark.php
```
## 👥 Credits & Acknowledgements
- [Colin O'Dell][@colinodell]
- [John MacFarlane][@jgm]
- [All Contributors]
This code is partially based on the [CommonMark JS reference implementation][commonmark.js] which is written, maintained and copyrighted by [John MacFarlane]. This project simply wouldn't exist without his work.
### Sponsors
We'd also like to extend our sincere thanks the following sponsors who support ongoing development of this project:
- [Tidelift](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) for offering support to both the maintainers and end-users through their [professional support](https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme) program
- [RIPS Technologies](https://www.ripstech.com/) for supporting this project with a complimentary [RIPS SaaS](https://www.ripstech.com/product/) license
- [JetBrains](https://www.jetbrains.com/) for supporting this project with complimentary [PhpStorm](https://www.jetbrains.com/phpstorm/) licenses
Are you interested in sponsoring development of this project? See <https://www.colinodell.com/sponsor> for a list of ways to contribute.
## 📄 License
**league/commonmark** is licensed under the BSD-3 license. See the [`LICENSE`](LICENSE) file for more details.
## 🏛️ Governance
This project is primarily maintained by [Colin O'Dell][@colinodell]. Members of the [PHP League] Leadership Team may occasionally assist with some of these duties.
## 🗺️ Who Uses It?
This project is used by [Drupal](https://www.drupal.org/project/markdown), [Laravel Framework](https://laravel.com/), [Cachet](https://cachethq.io/), [Firefly III](https://firefly-iii.org/), [Neos](https://www.neos.io/), [Daux.io](https://daux.io/), and [more](https://packagist.org/packages/league/commonmark/dependents)!
---
<div align="center">
<b>
<a href="https://tidelift.com/subscription/pkg/packagist-league-commonmark?utm_source=packagist-league-commonmark&utm_medium=referral&utm_campaign=readme">Get professional support for league/commonmark with a Tidelift subscription</a>
</b>
<br>
<sub>
Tidelift helps make open source sustainable for maintainers while giving companies<br>assurances about security, maintenance, and licensing for their dependencies.
</sub>
</div>
[CommonMark]: http://commonmark.org/
[CommonMark spec]: http://spec.commonmark.org/
[commonmark.js]: https://github.com/jgm/commonmark.js
[Github-Flavored Markdown]: https://github.github.com/gfm/
[John MacFarlane]: http://johnmacfarlane.net
[docs]: https://commonmark.thephpleague.com/
[docs-examples]: https://commonmark.thephpleague.com/customization/overview/#examples
[docs-example-twitter]: https://commonmark.thephpleague.com/customization/inline-parsing#example-1---twitter-handles
[docs-example-smilies]: https://commonmark.thephpleague.com/customization/inline-parsing#example-2---emoticons
[All Contributors]: https://github.com/thephpleague/commonmark/contributors
[@colinodell]: https://www.twitter.com/colinodell
[@jgm]: https://github.com/jgm
[jgm/stmd]: https://github.com/jgm/stmd
[Composer]: https://getcomposer.org/
[PHP League]: https://thephpleague.com

181
vendor/league/commonmark/bin/commonmark vendored Executable file
View File

@@ -0,0 +1,181 @@
#!/usr/bin/env php
<?php
trigger_error('The "bin/commonmark" command has been deprecated since league/commonmark 1.4', E_USER_DEPRECATED);
requireAutoloader();
ini_set('display_errors', 'stderr');
$options = array();
$options_raw = getopt('', array(
'use-asterisk',
'use-underscore',
'enable-strong',
'enable-em',
'safe',
));
foreach ($options_raw as $option => $value) {
switch ($option) {
case 'safe':
$options['html_input'] = 'strip';
$options['allow_unsafe_links'] = false;
break;
case 'use-asterisk':
case 'use-underscore':
case 'enable-strong':
case 'enable-em':
if ($value !== true && $value !== false) {
fail("Invalid value '$value' for option '$option'");
}
break;
}
$options[str_replace('-', '_', $option)] = $value;
}
foreach ($argv as $i => $arg) {
if ($i === 0) {
continue;
}
if (substr($arg, 0, 1) === '-') {
switch ($arg) {
case '-h':
case '--help':
echo getHelpText();
exit(0);
case '-v':
case '--version':
echo \League\CommonMark\CommonMarkConverter::VERSION . "\n";
exit(0);
case '--safe':
$options['safe'] = true;
break;
default:
$option = explode('=', $arg, 2)[0];
if (!preg_match('/^--[^-]/', $option)
|| !array_key_exists(ltrim($option, '-'), $options_raw)) {
fail('Unknown option: ' . $arg);
}
}
} else {
$src = $arg;
}
}
if (isset($src)) {
if (!file_exists($src)) {
fail('File not found: ' . $src);
}
$markdown = file_get_contents($src);
} else {
$stdin = fopen('php://stdin', 'r');
if (stream_set_blocking($stdin, false)) {
$markdown = stream_get_contents($stdin);
}
fclose($stdin);
if (empty($markdown)) {
fail(getHelpText());
}
}
$converter = new \League\CommonMark\CommonMarkConverter($options);
echo $converter->convertToHtml($markdown);
/**
* Get help and usage info
*
* @return string
*/
function getHelpText()
{
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
return <<<WINDOWSHELP
CommonMark - Markdown done right
Usage: commonmark [OPTIONS] [FILE]
-h, --help Shows help and usage information
-v, --version Shows the currently installed version
(Reading data from STDIN is not currently supported on Windows)
Examples:
Converting a file named document.md:
commonmark document.md
Converting a file and saving its output:
commonmark document.md > output.html
Full documentation can be found at http://commonmark.thephpleague.com/
WINDOWSHELP;
}
return <<<HELP
CommonMark - Markdown done right
Usage: commonmark [OPTIONS] [FILE]
-h, --help Shows help and usage information
-v, --version Shows the currently installed version
--safe Escapes all raw HTML input and removes unsafe URLs
If no file is given, input will be read from STDIN
Examples:
Converting a file named document.md:
commonmark document.md
Converting a file and saving its output:
commonmark document.md > output.html
Converting from STDIN:
echo -e '# Hello World!' | commonmark
Converting from STDIN and saving the output:
echo -e '# Hello World!' | commonmark > output.html
Full documentation can be found at http://commonmark.thephpleague.com/
HELP;
}
/**
* @param string $message Error message
*/
function fail($message)
{
fwrite(STDERR, $message . "\n");
exit(1);
}
function requireAutoloader()
{
$autoloadPaths = [
// Local package usage
__DIR__ . '/../vendor/autoload.php',
// Package was included as a library
__DIR__ . '/../../../autoload.php',
];
foreach ($autoloadPaths as $path) {
if (file_exists($path)) {
require_once $path;
break;
}
}
}

91
vendor/league/commonmark/composer.json vendored Normal file
View File

@@ -0,0 +1,91 @@
{
"name": "league/commonmark",
"type": "library",
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)",
"keywords": ["markdown","parser","commonmark","gfm","github","flavored","github-flavored","md"],
"homepage": "https://commonmark.thephpleague.com",
"license": "BSD-3-Clause",
"authors": [
{
"name": "Colin O'Dell",
"email": "colinodell@gmail.com",
"homepage": "https://www.colinodell.com",
"role": "Lead Developer"
}
],
"support": {
"docs": "https://commonmark.thephpleague.com/",
"issues": "https://github.com/thephpleague/commonmark/issues",
"rss": "https://github.com/thephpleague/commonmark/releases.atom",
"source": "https://github.com/thephpleague/commonmark"
},
"require": {
"php": "^7.1 || ^8.0",
"ext-mbstring": "*"
},
"require-dev": {
"ext-json": "*",
"cebe/markdown": "~1.0",
"commonmark/commonmark.js": "0.29.2",
"erusev/parsedown": "~1.0",
"github/gfm": "0.29.0",
"michelf/php-markdown": "~1.4",
"mikehaertl/php-shellcommand": "^1.4",
"phpstan/phpstan": "^0.12.90",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.2",
"scrutinizer/ocular": "^1.5",
"symfony/finder": "^4.2"
},
"minimum-stability": "dev",
"prefer-stable": true,
"conflict": {
"scrutinizer/ocular": "1.7.*"
},
"repositories": [
{
"type": "package",
"package": {
"name": "commonmark/commonmark.js",
"version": "0.29.2",
"dist": {
"url": "https://github.com/commonmark/commonmark.js/archive/0.29.2.zip",
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "github/gfm",
"version": "0.29.0",
"dist": {
"url": "https://github.com/github/cmark-gfm/archive/0.29.0.gfm.0.zip",
"type": "zip"
}
}
}
],
"autoload": {
"psr-4": {
"League\\CommonMark\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"League\\CommonMark\\Tests\\Unit\\": "tests/unit",
"League\\CommonMark\\Tests\\Functional\\": "tests/functional"
}
},
"bin": ["bin/commonmark"],
"scripts": {
"phpstan": "phpstan analyse",
"phpunit": "phpunit --no-coverage",
"test": [
"@phpstan",
"@phpunit"
]
},
"config": {
"sort-packages": true
}
}

View File

@@ -0,0 +1,222 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Node\Node;
/**
* Block-level element
*
* @method parent() ?AbstractBlock
*/
abstract class AbstractBlock extends Node
{
/**
* Used for storage of arbitrary data.
*
* @var array<string, mixed>
*/
public $data = [];
/**
* @var bool
*/
protected $open = true;
/**
* @var bool
*/
protected $lastLineBlank = false;
/**
* @var int
*/
protected $startLine = 0;
/**
* @var int
*/
protected $endLine = 0;
protected function setParent(Node $node = null)
{
if ($node && !$node instanceof self) {
throw new \InvalidArgumentException('Parent of block must also be block (can not be inline)');
}
parent::setParent($node);
}
public function isContainer(): bool
{
return true;
}
/**
* @return bool
*/
public function hasChildren(): bool
{
return $this->firstChild !== null;
}
/**
* Returns true if this block can contain the given block as a child node
*
* @param AbstractBlock $block
*
* @return bool
*/
abstract public function canContain(AbstractBlock $block): bool;
/**
* Whether this is a code block
*
* Code blocks are extra-greedy - they'll try to consume all subsequent
* lines of content without calling matchesNextLine() each time.
*
* @return bool
*/
abstract public function isCode(): bool;
/**
* @param Cursor $cursor
*
* @return bool
*/
abstract public function matchesNextLine(Cursor $cursor): bool;
/**
* @param int $startLine
*
* @return $this
*/
public function setStartLine(int $startLine)
{
$this->startLine = $startLine;
if (empty($this->endLine)) {
$this->endLine = $startLine;
}
return $this;
}
/**
* @return int
*/
public function getStartLine(): int
{
return $this->startLine;
}
/**
* @param int $endLine
*
* @return $this
*/
public function setEndLine(int $endLine)
{
$this->endLine = $endLine;
return $this;
}
/**
* @return int
*/
public function getEndLine(): int
{
return $this->endLine;
}
/**
* Whether the block ends with a blank line
*
* @return bool
*/
public function endsWithBlankLine(): bool
{
return $this->lastLineBlank;
}
/**
* @param bool $blank
*
* @return void
*/
public function setLastLineBlank(bool $blank)
{
$this->lastLineBlank = $blank;
}
/**
* Determines whether the last line should be marked as blank
*
* @param Cursor $cursor
* @param int $currentLineNumber
*
* @return bool
*/
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return $cursor->isBlank();
}
/**
* Whether the block is open for modifications
*
* @return bool
*/
public function isOpen(): bool
{
return $this->open;
}
/**
* Finalize the block; mark it closed for modification
*
* @param ContextInterface $context
* @param int $endLineNumber
*
* @return void
*/
public function finalize(ContextInterface $context, int $endLineNumber)
{
if (!$this->open) {
return;
}
$this->open = false;
$this->endLine = $endLineNumber;
// This should almost always be true
if ($context->getTip() !== null) {
$context->setTip($context->getTip()->parent());
}
}
/**
* @param string $key
* @param mixed $default
*
* @return mixed
*/
public function getData(string $key, $default = null)
{
return \array_key_exists($key, $this->data) ? $this->data[$key] : $default;
}
}

View File

@@ -0,0 +1,55 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\ArrayCollection;
/**
* @method children() AbstractInline[]
*/
abstract class AbstractStringContainerBlock extends AbstractBlock implements StringContainerInterface
{
/**
* @var ArrayCollection<int, string>
*/
protected $strings;
/**
* @var string
*/
protected $finalStringContents = '';
/**
* Constructor
*/
public function __construct()
{
$this->strings = new ArrayCollection();
}
public function addLine(string $line)
{
$this->strings[] = $line;
}
abstract public function handleRemainingContents(ContextInterface $context, Cursor $cursor);
public function getStringContent(): string
{
return $this->finalStringContents;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\Cursor;
/**
* @method children() AbstractBlock[]
*/
class BlockQuote extends AbstractBlock
{
public function canContain(AbstractBlock $block): bool
{
return true;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
if (!$cursor->isIndented() && $cursor->getNextNonSpaceCharacter() === '>') {
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(1);
$cursor->advanceBySpaceOrTab();
return true;
}
return false;
}
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return false;
}
}

View File

@@ -0,0 +1,58 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\Cursor;
use League\CommonMark\Reference\ReferenceMap;
use League\CommonMark\Reference\ReferenceMapInterface;
/**
* @method children() AbstractBlock[]
*/
class Document extends AbstractBlock
{
/** @var ReferenceMapInterface */
protected $referenceMap;
public function __construct(?ReferenceMapInterface $referenceMap = null)
{
$this->setStartLine(1);
$this->referenceMap = $referenceMap ?? new ReferenceMap();
}
/**
* @return ReferenceMapInterface
*/
public function getReferenceMap(): ReferenceMapInterface
{
return $this->referenceMap;
}
public function canContain(AbstractBlock $block): bool
{
return true;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return true;
}
}

View File

@@ -0,0 +1,201 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\RegexHelper;
class FencedCode extends AbstractStringContainerBlock
{
/**
* @var string
*/
protected $info;
/**
* @var int
*/
protected $length;
/**
* @var string
*/
protected $char;
/**
* @var int
*/
protected $offset;
/**
* @param int $length
* @param string $char
* @param int $offset
*/
public function __construct(int $length, string $char, int $offset)
{
parent::__construct();
$this->length = $length;
$this->char = $char;
$this->offset = $offset;
}
/**
* @return string
*/
public function getInfo(): string
{
return $this->info;
}
/**
* @return string[]
*/
public function getInfoWords(): array
{
return \preg_split('/\s+/', $this->info) ?: [];
}
/**
* @return string
*/
public function getChar(): string
{
return $this->char;
}
/**
* @param string $char
*
* @return $this
*/
public function setChar(string $char): self
{
$this->char = $char;
return $this;
}
/**
* @return int
*/
public function getLength(): int
{
return $this->length;
}
/**
* @param int $length
*
* @return $this
*/
public function setLength(int $length): self
{
$this->length = $length;
return $this;
}
/**
* @return int
*/
public function getOffset(): int
{
return $this->offset;
}
/**
* @param int $offset
*
* @return $this
*/
public function setOffset(int $offset): self
{
$this->offset = $offset;
return $this;
}
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return true;
}
public function matchesNextLine(Cursor $cursor): bool
{
if ($this->length === -1) {
if ($cursor->isBlank()) {
$this->lastLineBlank = true;
}
return false;
}
// Skip optional spaces of fence offset
$cursor->match('/^ {0,' . $this->offset . '}/');
return true;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
// first line becomes info string
$firstLine = $this->strings->first();
if ($firstLine === false) {
$firstLine = '';
}
$this->info = RegexHelper::unescape(\trim($firstLine));
if ($this->strings->count() === 1) {
$this->finalStringContents = '';
} else {
$this->finalStringContents = \implode("\n", $this->strings->slice(1)) . "\n";
}
}
public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
{
/** @var self $container */
$container = $context->getContainer();
// check for closing code fence
if ($cursor->getIndent() <= 3 && $cursor->getNextNonSpaceCharacter() === $container->getChar()) {
$match = RegexHelper::matchFirst('/^(?:`{3,}|~{3,})(?= *$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition());
if ($match !== null && \strlen($match[0]) >= $container->getLength()) {
// don't add closing fence to container; instead, close it:
$this->setLength(-1); // -1 means we've passed closer
return;
}
}
$container->addLine($cursor->getRemainder());
}
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return false;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
class Heading extends AbstractStringContainerBlock implements InlineContainerInterface
{
/**
* @var int
*/
protected $level;
/**
* @param int $level
* @param string|string[] $contents
*/
public function __construct(int $level, $contents)
{
parent::__construct();
$this->level = $level;
if (!\is_array($contents)) {
$contents = [$contents];
}
foreach ($contents as $line) {
$this->addLine($line);
}
}
/**
* @return int
*/
public function getLevel(): int
{
return $this->level;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
$this->finalStringContents = \implode("\n", $this->strings->toArray());
}
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
{
// nothing to do; contents were already added via the constructor.
}
}

View File

@@ -0,0 +1,104 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\RegexHelper;
class HtmlBlock extends AbstractStringContainerBlock
{
// Any changes to these constants should be reflected in .phpstorm.meta.php
const TYPE_1_CODE_CONTAINER = 1;
const TYPE_2_COMMENT = 2;
const TYPE_3 = 3;
const TYPE_4 = 4;
const TYPE_5_CDATA = 5;
const TYPE_6_BLOCK_ELEMENT = 6;
const TYPE_7_MISC_ELEMENT = 7;
/**
* @var int
*/
protected $type;
/**
* @param int $type
*/
public function __construct(int $type)
{
parent::__construct();
$this->type = $type;
}
/**
* @return int
*/
public function getType(): int
{
return $this->type;
}
/**
* @param int $type
*
* @return void
*/
public function setType(int $type)
{
$this->type = $type;
}
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return true;
}
public function matchesNextLine(Cursor $cursor): bool
{
if ($cursor->isBlank() && ($this->type === self::TYPE_6_BLOCK_ELEMENT || $this->type === self::TYPE_7_MISC_ELEMENT)) {
return false;
}
return true;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
$this->finalStringContents = \implode("\n", $this->strings->toArray());
}
public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
{
/** @var self $tip */
$tip = $context->getTip();
$tip->addLine($cursor->getRemainder());
// Check for end condition
if ($this->type >= self::TYPE_1_CODE_CONTAINER && $this->type <= self::TYPE_5_CDATA) {
if ($cursor->match(RegexHelper::getHtmlBlockCloseRegex($this->type)) !== null) {
$this->finalize($context, $context->getLineNumber());
}
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
class IndentedCode extends AbstractStringContainerBlock
{
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return true;
}
public function matchesNextLine(Cursor $cursor): bool
{
if ($cursor->isIndented()) {
$cursor->advanceBy(Cursor::INDENT_LEVEL, true);
} elseif ($cursor->isBlank()) {
$cursor->advanceToNextNonSpaceOrTab();
} else {
return false;
}
return true;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
$reversed = \array_reverse($this->strings->toArray(), true);
foreach ($reversed as $index => $line) {
if ($line === '' || $line === "\n" || \preg_match('/^(\n *)$/', $line)) {
unset($reversed[$index]);
} else {
break;
}
}
$fixed = \array_reverse($reversed);
$tmp = \implode("\n", $fixed);
if (\substr($tmp, -1) !== "\n") {
$tmp .= "\n";
}
$this->finalStringContents = $tmp;
}
public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
{
/** @var self $tip */
$tip = $context->getTip();
$tip->addLine($cursor->getRemainder());
}
}

View File

@@ -0,0 +1,20 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
interface InlineContainerInterface extends StringContainerInterface
{
public function getStringContent(): string;
}

View File

@@ -0,0 +1,123 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
/**
* @method children() AbstractBlock[]
*/
class ListBlock extends AbstractBlock
{
const TYPE_BULLET = 'bullet';
const TYPE_ORDERED = 'ordered';
/**
* @deprecated This constant is deprecated in league/commonmark 1.4 and will be removed in 2.0; use TYPE_BULLET instead
*/
const TYPE_UNORDERED = self::TYPE_BULLET;
/**
* @var bool
*/
protected $tight = false;
/**
* @var ListData
*/
protected $listData;
public function __construct(ListData $listData)
{
$this->listData = $listData;
}
/**
* @return ListData
*/
public function getListData(): ListData
{
return $this->listData;
}
public function endsWithBlankLine(): bool
{
if ($this->lastLineBlank) {
return true;
}
if ($this->hasChildren()) {
return $this->lastChild() instanceof AbstractBlock && $this->lastChild()->endsWithBlankLine();
}
return false;
}
public function canContain(AbstractBlock $block): bool
{
return $block instanceof ListItem;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return true;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
$this->tight = true; // tight by default
foreach ($this->children() as $item) {
if (!($item instanceof AbstractBlock)) {
continue;
}
// check for non-final list item ending with blank line:
if ($item->endsWithBlankLine() && $item !== $this->lastChild()) {
$this->tight = false;
break;
}
// Recurse into children of list item, to see if there are
// spaces between any of them:
foreach ($item->children() as $subItem) {
if ($subItem instanceof AbstractBlock && $subItem->endsWithBlankLine() && ($item !== $this->lastChild() || $subItem !== $item->lastChild())) {
$this->tight = false;
break;
}
}
}
}
public function isTight(): bool
{
return $this->tight;
}
public function setTight(bool $tight): self
{
$this->tight = $tight;
return $this;
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
class ListData
{
/**
* @var int|null
*/
public $start;
/**
* @var int
*/
public $padding = 0;
/**
* @var string
*/
public $type;
/**
* @var string|null
*/
public $delimiter;
/**
* @var string|null
*/
public $bulletChar;
/**
* @var int
*/
public $markerOffset;
/**
* @param ListData $data
*
* @return bool
*/
public function equals(ListData $data)
{
return $this->type === $data->type &&
$this->delimiter === $data->delimiter &&
$this->bulletChar === $data->bulletChar;
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\Cursor;
/**
* @method children() AbstractBlock[]
*/
class ListItem extends AbstractBlock
{
/**
* @var ListData
*/
protected $listData;
public function __construct(ListData $listData)
{
$this->listData = $listData;
}
/**
* @return ListData
*/
public function getListData(): ListData
{
return $this->listData;
}
public function canContain(AbstractBlock $block): bool
{
return true;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
if ($cursor->isBlank()) {
if ($this->firstChild === null) {
return false;
}
$cursor->advanceToNextNonSpaceOrTab();
} elseif ($cursor->getIndent() >= $this->listData->markerOffset + $this->listData->padding) {
$cursor->advanceBy($this->listData->markerOffset + $this->listData->padding, true);
} else {
return false;
}
return true;
}
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return $cursor->isBlank() && $this->startLine < $currentLineNumber;
}
}

View File

@@ -0,0 +1,98 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
class Paragraph extends AbstractStringContainerBlock implements InlineContainerInterface
{
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
if ($cursor->isBlank()) {
$this->lastLineBlank = true;
return false;
}
return true;
}
public function finalize(ContextInterface $context, int $endLineNumber)
{
parent::finalize($context, $endLineNumber);
$this->finalStringContents = \preg_replace('/^ */m', '', \implode("\n", $this->getStrings()));
// Short-circuit
if ($this->finalStringContents === '' || $this->finalStringContents[0] !== '[') {
return;
}
$cursor = new Cursor($this->finalStringContents);
$referenceFound = $this->parseReferences($context, $cursor);
$this->finalStringContents = $cursor->getRemainder();
if ($referenceFound && $cursor->isAtEnd()) {
$this->detach();
}
}
/**
* @param ContextInterface $context
* @param Cursor $cursor
*
* @return bool
*/
protected function parseReferences(ContextInterface $context, Cursor $cursor)
{
$referenceFound = false;
while ($cursor->getCharacter() === '[' && $context->getReferenceParser()->parse($cursor)) {
$this->finalStringContents = $cursor->getRemainder();
$referenceFound = true;
}
return $referenceFound;
}
public function handleRemainingContents(ContextInterface $context, Cursor $cursor)
{
$cursor->advanceToNextNonSpaceOrTab();
/** @var self $tip */
$tip = $context->getTip();
$tip->addLine($cursor->getRemainder());
}
/**
* @return string[]
*/
public function getStrings(): array
{
return $this->strings->toArray();
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
/**
* Interface for a block which can contain line(s) of strings
*/
interface StringContainerInterface
{
/**
* @param string $line
*
* @return void
*/
public function addLine(string $line);
/**
* @return string
*/
public function getStringContent(): string;
/**
* @param ContextInterface $context
* @param Cursor $cursor
*
* @return void
*/
public function handleRemainingContents(ContextInterface $context, Cursor $cursor);
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
use League\CommonMark\Cursor;
class ThematicBreak extends AbstractBlock
{
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
}

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\Heading;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\RegexHelper;
final class ATXHeadingParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
$match = RegexHelper::matchFirst('/^#{1,6}(?:[ \t]+|$)/', $cursor->getLine(), $cursor->getNextNonSpacePosition());
if (!$match) {
return false;
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(\strlen($match[0]));
$level = \strlen(\trim($match[0]));
$str = $cursor->getRemainder();
/** @var string $str */
$str = \preg_replace('/^[ \t]*#+[ \t]*$/', '', $str);
/** @var string $str */
$str = \preg_replace('/[ \t]+#+[ \t]*$/', '', $str);
$context->addBlock(new Heading($level, $str));
$context->setBlocksParsed(true);
return true;
}
}

View File

@@ -0,0 +1,29 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
interface BlockParserInterface
{
/**
* @param ContextInterface $context
* @param Cursor $cursor
*
* @return bool
*/
public function parse(ContextInterface $context, Cursor $cursor): bool;
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\BlockQuote;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
final class BlockQuoteParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
if ($cursor->getNextNonSpaceCharacter() !== '>') {
return false;
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(1);
$cursor->advanceBySpaceOrTab();
$context->addBlock(new BlockQuote());
return true;
}
}

View File

@@ -0,0 +1,47 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\FencedCode;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
final class FencedCodeParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
$c = $cursor->getCharacter();
if ($c !== ' ' && $c !== "\t" && $c !== '`' && $c !== '~') {
return false;
}
$indent = $cursor->getIndent();
$fence = $cursor->match('/^[ \t]*(?:`{3,}(?!.*`)|~{3,})/');
if ($fence === null) {
return false;
}
// fenced code block
$fence = \ltrim($fence, " \t");
$fenceLength = \strlen($fence);
$context->addBlock(new FencedCode($fenceLength, $fence[0], $indent));
return true;
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\HtmlBlock;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\RegexHelper;
final class HtmlBlockParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
if ($cursor->getNextNonSpaceCharacter() !== '<') {
return false;
}
$savedState = $cursor->saveState();
$cursor->advanceToNextNonSpaceOrTab();
$line = $cursor->getRemainder();
for ($blockType = 1; $blockType <= 7; $blockType++) {
$match = RegexHelper::matchAt(
RegexHelper::getHtmlBlockOpenRegex($blockType),
$line
);
if ($match !== null && ($blockType < 7 || !($context->getContainer() instanceof Paragraph))) {
$cursor->restoreState($savedState);
$context->addBlock(new HtmlBlock($blockType));
$context->setBlocksParsed(true);
return true;
}
}
$cursor->restoreState($savedState);
return false;
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\IndentedCode;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
final class IndentedCodeParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if (!$cursor->isIndented()) {
return false;
}
if ($context->getTip() instanceof Paragraph) {
return false;
}
if ($cursor->isBlank()) {
return false;
}
$cursor->advanceBy(Cursor::INDENT_LEVEL, true);
$context->addBlock(new IndentedCode());
return true;
}
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
final class LazyParagraphParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if (!$cursor->isIndented()) {
return false;
}
$context->setBlocksParsed(true);
return true;
}
}

View File

@@ -0,0 +1,154 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\ListBlock;
use League\CommonMark\Block\Element\ListData;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
use League\CommonMark\Util\RegexHelper;
final class ListParser implements BlockParserInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface|null */
private $config;
/** @var string|null */
private $listMarkerRegex;
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented() && !($context->getContainer() instanceof ListBlock)) {
return false;
}
$indent = $cursor->getIndent();
if ($indent >= 4) {
return false;
}
$tmpCursor = clone $cursor;
$tmpCursor->advanceToNextNonSpaceOrTab();
$rest = $tmpCursor->getRemainder();
if (\preg_match($this->listMarkerRegex ?? $this->generateListMarkerRegex(), $rest) === 1) {
$data = new ListData();
$data->markerOffset = $indent;
$data->type = ListBlock::TYPE_BULLET;
$data->delimiter = null;
$data->bulletChar = $rest[0];
$markerLength = 1;
} elseif (($matches = RegexHelper::matchFirst('/^(\d{1,9})([.)])/', $rest)) && (!($context->getContainer() instanceof Paragraph) || $matches[1] === '1')) {
$data = new ListData();
$data->markerOffset = $indent;
$data->type = ListBlock::TYPE_ORDERED;
$data->start = (int) $matches[1];
$data->delimiter = $matches[2];
$data->bulletChar = null;
$markerLength = \strlen($matches[0]);
} else {
return false;
}
// Make sure we have spaces after
$nextChar = $tmpCursor->peek($markerLength);
if (!($nextChar === null || $nextChar === "\t" || $nextChar === ' ')) {
return false;
}
// If it interrupts paragraph, make sure first line isn't blank
$container = $context->getContainer();
if ($container instanceof Paragraph && !RegexHelper::matchAt(RegexHelper::REGEX_NON_SPACE, $rest, $markerLength)) {
return false;
}
// We've got a match! Advance offset and calculate padding
$cursor->advanceToNextNonSpaceOrTab(); // to start of marker
$cursor->advanceBy($markerLength, true); // to end of marker
$data->padding = $this->calculateListMarkerPadding($cursor, $markerLength);
// add the list if needed
if (!($container instanceof ListBlock) || !$data->equals($container->getListData())) {
$context->addBlock(new ListBlock($data));
}
// add the list item
$context->addBlock(new ListItem($data));
return true;
}
/**
* @param Cursor $cursor
* @param int $markerLength
*
* @return int
*/
private function calculateListMarkerPadding(Cursor $cursor, int $markerLength): int
{
$start = $cursor->saveState();
$spacesStartCol = $cursor->getColumn();
while ($cursor->getColumn() - $spacesStartCol < 5) {
if (!$cursor->advanceBySpaceOrTab()) {
break;
}
}
$blankItem = $cursor->peek() === null;
$spacesAfterMarker = $cursor->getColumn() - $spacesStartCol;
if ($spacesAfterMarker >= 5 || $spacesAfterMarker < 1 || $blankItem) {
$cursor->restoreState($start);
$cursor->advanceBySpaceOrTab();
return $markerLength + 1;
}
return $markerLength + $spacesAfterMarker;
}
private function generateListMarkerRegex(): string
{
// No configuration given - use the defaults
if ($this->config === null) {
return $this->listMarkerRegex = '/^[*+-]/';
}
$deprecatedMarkers = $this->config->get('unordered_list_markers', ConfigurationInterface::MISSING);
if ($deprecatedMarkers !== ConfigurationInterface::MISSING) {
@\trigger_error('The "unordered_list_markers" configuration option is deprecated in league/commonmark 1.6 and will be replaced with "commonmark > unordered_list_markers" in 2.0', \E_USER_DEPRECATED);
} else {
$deprecatedMarkers = ['*', '+', '-'];
}
$markers = $this->config->get('commonmark/unordered_list_markers', $deprecatedMarkers);
if (!\is_array($markers) || $markers === []) {
throw new \RuntimeException('Invalid configuration option "unordered_list_markers": value must be an array of strings');
}
return $this->listMarkerRegex = '/^[' . \preg_quote(\implode('', $markers), '/') . ']/';
}
}

View File

@@ -0,0 +1,81 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\Heading;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Reference\ReferenceParser;
use League\CommonMark\Util\RegexHelper;
final class SetExtHeadingParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
if (!($context->getContainer() instanceof Paragraph)) {
return false;
}
$match = RegexHelper::matchFirst('/^(?:=+|-+)[ \t]*$/', $cursor->getLine(), $cursor->getNextNonSpacePosition());
if ($match === null) {
return false;
}
$level = $match[0][0] === '=' ? 1 : 2;
$strings = $context->getContainer()->getStrings();
$strings = $this->resolveReferenceLinkDefinitions($strings, $context->getReferenceParser());
if (empty($strings)) {
return false;
}
$context->replaceContainerBlock(new Heading($level, $strings));
return true;
}
/**
* Resolve reference link definition
*
* @see https://github.com/commonmark/commonmark.js/commit/993bbe335931af847460effa99b2411eb643577d
*
* @param string[] $strings
* @param ReferenceParser $referenceParser
*
* @return string[]
*/
private function resolveReferenceLinkDefinitions(array $strings, ReferenceParser $referenceParser): array
{
foreach ($strings as &$string) {
$cursor = new Cursor($string);
while ($cursor->getCharacter() === '[' && $referenceParser->parse($cursor)) {
$string = $cursor->getRemainder();
}
if ($string !== '') {
break;
}
}
return \array_filter($strings, function ($s) {
return $s !== '';
});
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Parser;
use League\CommonMark\Block\Element\ThematicBreak;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Util\RegexHelper;
final class ThematicBreakParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
$match = RegexHelper::matchAt(RegexHelper::REGEX_THEMATIC_BREAK, $cursor->getLine(), $cursor->getNextNonSpacePosition());
if ($match === null) {
return false;
}
// Advance to the end of the string, consuming the entire line (of the thematic break)
$cursor->advanceToEnd();
$context->addBlock(new ThematicBreak());
$context->setBlocksParsed(true);
return true;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\BlockQuote;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
final class BlockQuoteRenderer implements BlockRendererInterface
{
/**
* @param BlockQuote $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof BlockQuote)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$attrs = $block->getData('attributes', []);
$filling = $htmlRenderer->renderBlocks($block->children());
if ($filling === '') {
return new HtmlElement('blockquote', $attrs, $htmlRenderer->getOption('inner_separator', "\n"));
}
return new HtmlElement(
'blockquote',
$attrs,
$htmlRenderer->getOption('inner_separator', "\n") . $filling . $htmlRenderer->getOption('inner_separator', "\n")
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
interface BlockRendererInterface
{
/**
* @param AbstractBlock $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement|string|null
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false);
}

View File

@@ -0,0 +1,40 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\ElementRendererInterface;
final class DocumentRenderer implements BlockRendererInterface
{
/**
* @param Document $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return string
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof Document)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$wholeDoc = $htmlRenderer->renderBlocks($block->children());
return $wholeDoc === '' ? '' : $wholeDoc . "\n";
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\FencedCode;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
use League\CommonMark\Util\Xml;
final class FencedCodeRenderer implements BlockRendererInterface
{
/**
* @param FencedCode $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof FencedCode)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$attrs = $block->getData('attributes', []);
$infoWords = $block->getInfoWords();
if (\count($infoWords) !== 0 && \strlen($infoWords[0]) !== 0) {
$attrs['class'] = isset($attrs['class']) ? $attrs['class'] . ' ' : '';
$attrs['class'] .= 'language-' . $infoWords[0];
}
return new HtmlElement(
'pre',
[],
new HtmlElement('code', $attrs, Xml::escape($block->getStringContent()))
);
}
}

View File

@@ -0,0 +1,43 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\Heading;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
final class HeadingRenderer implements BlockRendererInterface
{
/**
* @param Heading $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof Heading)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$tag = 'h' . $block->getLevel();
$attrs = $block->getData('attributes', []);
return new HtmlElement($tag, $attrs, $htmlRenderer->renderInlines($block->children()));
}
}

View File

@@ -0,0 +1,59 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\HtmlBlock;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\EnvironmentInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class HtmlBlockRenderer implements BlockRendererInterface, ConfigurationAwareInterface
{
/**
* @var ConfigurationInterface
*/
protected $config;
/**
* @param HtmlBlock $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return string
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof HtmlBlock)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
if ($this->config->get('html_input') === EnvironmentInterface::HTML_INPUT_STRIP) {
return '';
}
if ($this->config->get('html_input') === EnvironmentInterface::HTML_INPUT_ESCAPE) {
return \htmlspecialchars($block->getStringContent(), \ENT_NOQUOTES);
}
return $block->getStringContent();
}
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\IndentedCode;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
use League\CommonMark\Util\Xml;
final class IndentedCodeRenderer implements BlockRendererInterface
{
/**
* @param IndentedCode $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof IndentedCode)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$attrs = $block->getData('attributes', []);
return new HtmlElement(
'pre',
[],
new HtmlElement('code', $attrs, Xml::escape($block->getStringContent()))
);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\ListBlock;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
final class ListBlockRenderer implements BlockRendererInterface
{
/**
* @param ListBlock $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof ListBlock)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$listData = $block->getListData();
$tag = $listData->type === ListBlock::TYPE_BULLET ? 'ul' : 'ol';
$attrs = $block->getData('attributes', []);
if ($listData->start !== null && $listData->start !== 1) {
$attrs['start'] = (string) $listData->start;
}
return new HtmlElement(
$tag,
$attrs,
$htmlRenderer->getOption('inner_separator', "\n") . $htmlRenderer->renderBlocks(
$block->children(),
$block->isTight()
) . $htmlRenderer->getOption('inner_separator', "\n")
);
}
}

View File

@@ -0,0 +1,60 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\TaskList\TaskListItemMarker;
use League\CommonMark\HtmlElement;
final class ListItemRenderer implements BlockRendererInterface
{
/**
* @param ListItem $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return string
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof ListItem)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$contents = $htmlRenderer->renderBlocks($block->children(), $inTightList);
if (\substr($contents, 0, 1) === '<' && !$this->startsTaskListItem($block)) {
$contents = "\n" . $contents;
}
if (\substr($contents, -1, 1) === '>') {
$contents .= "\n";
}
$attrs = $block->getData('attributes', []);
$li = new HtmlElement('li', $attrs, $contents);
return $li;
}
private function startsTaskListItem(ListItem $block): bool
{
$firstChild = $block->firstChild();
return $firstChild instanceof Paragraph && $firstChild->firstChild() instanceof TaskListItemMarker;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
final class ParagraphRenderer implements BlockRendererInterface
{
/**
* @param Paragraph $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement|string
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof Paragraph)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
if ($inTightList) {
return $htmlRenderer->renderInlines($block->children());
}
$attrs = $block->getData('attributes', []);
return new HtmlElement('p', $attrs, $htmlRenderer->renderInlines($block->children()));
}
}

View File

@@ -0,0 +1,41 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\ThematicBreak;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\HtmlElement;
final class ThematicBreakRenderer implements BlockRendererInterface
{
/**
* @param ThematicBreak $block
* @param ElementRendererInterface $htmlRenderer
* @param bool $inTightList
*
* @return HtmlElement
*/
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof ThematicBreak)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$attrs = $block->getData('attributes', []);
return new HtmlElement('hr', $attrs, '', true);
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
/**
* Converts CommonMark-compatible Markdown to HTML.
*/
class CommonMarkConverter extends MarkdownConverter
{
/**
* The currently-installed version.
*
* This might be a typical `x.y.z` version, or `x.y-dev`.
*
* @deprecated in 1.5.0 and will be removed from 2.0.0.
* Use \Composer\InstalledVersions provided by composer-runtime-api instead.
*/
public const VERSION = '1.6.7';
/**
* Create a new commonmark converter instance.
*
* @param array<string, mixed> $config
* @param EnvironmentInterface|null $environment
*/
public function __construct(array $config = [], EnvironmentInterface $environment = null)
{
if ($environment === null) {
$environment = Environment::createCommonMarkEnvironment();
} else {
@\trigger_error(\sprintf('Passing an $environment into the "%s" constructor is deprecated in 1.6 and will not be supported in 2.0; use MarkdownConverter instead. See https://commonmark.thephpleague.com/2.0/upgrading/consumers/#commonmarkconverter-and-githubflavoredmarkdownconverter-constructors for more details.', self::class), \E_USER_DEPRECATED);
}
if ($environment instanceof ConfigurableEnvironmentInterface) {
$environment->mergeConfig($config);
}
parent::__construct($environment);
}
}

View File

@@ -0,0 +1,110 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
/**
* Interface for an Environment which can be configured with config settings, parsers, processors, and renderers
*/
interface ConfigurableEnvironmentInterface extends EnvironmentInterface
{
/**
* @param array<string, mixed> $config
*
* @return void
*/
public function mergeConfig(array $config = []);
/**
* @param array<string, mixed> $config
*
* @return void
*
* @deprecated in 1.6 and will be removed in 2.0; use mergeConfig() instead
*/
public function setConfig(array $config = []);
/**
* Registers the given extension with the Environment
*
* @param ExtensionInterface $extension
*
* @return ConfigurableEnvironmentInterface
*/
public function addExtension(ExtensionInterface $extension): ConfigurableEnvironmentInterface;
/**
* Registers the given block parser with the Environment
*
* @param BlockParserInterface $parser Block parser instance
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return self
*/
public function addBlockParser(BlockParserInterface $parser, int $priority = 0): ConfigurableEnvironmentInterface;
/**
* Registers the given inline parser with the Environment
*
* @param InlineParserInterface $parser Inline parser instance
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return self
*/
public function addInlineParser(InlineParserInterface $parser, int $priority = 0): ConfigurableEnvironmentInterface;
/**
* Registers the given delimiter processor with the Environment
*
* @param DelimiterProcessorInterface $processor Delimiter processors instance
*
* @return ConfigurableEnvironmentInterface
*/
public function addDelimiterProcessor(DelimiterProcessorInterface $processor): ConfigurableEnvironmentInterface;
/**
* @param string $blockClass The fully-qualified block element class name the renderer below should handle
* @param BlockRendererInterface $blockRenderer The renderer responsible for rendering the type of element given above
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return self
*/
public function addBlockRenderer($blockClass, BlockRendererInterface $blockRenderer, int $priority = 0): ConfigurableEnvironmentInterface;
/**
* Registers the given inline renderer with the Environment
*
* @param string $inlineClass The fully-qualified inline element class name the renderer below should handle
* @param InlineRendererInterface $renderer The renderer responsible for rendering the type of element given above
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return self
*/
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0): ConfigurableEnvironmentInterface;
/**
* Registers the given event listener
*
* @param string $eventClass Fully-qualified class name of the event this listener should respond to
* @param callable $listener Listener to be executed
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return self
*/
public function addEventListener(string $eventClass, callable $listener, int $priority = 0): ConfigurableEnvironmentInterface;
}

201
vendor/league/commonmark/src/Context.php vendored Normal file
View File

@@ -0,0 +1,201 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Reference\ReferenceParser;
/**
* Maintains the current state of the Markdown parser engine
*/
class Context implements ContextInterface
{
/**
* @var EnvironmentInterface
*/
protected $environment;
/**
* @var Document
*/
protected $doc;
/**
* @var AbstractBlock|null
*/
protected $tip;
/**
* @var AbstractBlock
*/
protected $container;
/**
* @var int
*/
protected $lineNumber;
/**
* @var string
*/
protected $line;
/**
* @var UnmatchedBlockCloser
*/
protected $blockCloser;
/**
* @var bool
*/
protected $blocksParsed = false;
/**
* @var ReferenceParser
*/
protected $referenceParser;
public function __construct(Document $document, EnvironmentInterface $environment)
{
$this->doc = $document;
$this->tip = $this->doc;
$this->container = $this->doc;
$this->environment = $environment;
$this->referenceParser = new ReferenceParser($document->getReferenceMap());
$this->blockCloser = new UnmatchedBlockCloser($this);
}
/**
* @param string $line
*
* @return void
*/
public function setNextLine(string $line)
{
++$this->lineNumber;
$this->line = $line;
}
public function getDocument(): Document
{
return $this->doc;
}
public function getTip(): ?AbstractBlock
{
return $this->tip;
}
/**
* @param AbstractBlock|null $block
*
* @return $this
*/
public function setTip(?AbstractBlock $block)
{
$this->tip = $block;
return $this;
}
public function getLineNumber(): int
{
return $this->lineNumber;
}
public function getLine(): string
{
return $this->line;
}
public function getBlockCloser(): UnmatchedBlockCloser
{
return $this->blockCloser;
}
public function getContainer(): AbstractBlock
{
return $this->container;
}
/**
* @param AbstractBlock $container
*
* @return $this
*/
public function setContainer(AbstractBlock $container)
{
$this->container = $container;
return $this;
}
public function addBlock(AbstractBlock $block)
{
$this->blockCloser->closeUnmatchedBlocks();
$block->setStartLine($this->lineNumber);
while ($this->tip !== null && !$this->tip->canContain($block)) {
$this->tip->finalize($this, $this->lineNumber);
}
// This should always be true
if ($this->tip !== null) {
$this->tip->appendChild($block);
}
$this->tip = $block;
$this->container = $block;
}
public function replaceContainerBlock(AbstractBlock $replacement)
{
$this->blockCloser->closeUnmatchedBlocks();
$replacement->setStartLine($this->container->getStartLine());
$this->container->replaceWith($replacement);
if ($this->tip === $this->container) {
$this->tip = $replacement;
}
$this->container = $replacement;
}
public function getBlocksParsed(): bool
{
return $this->blocksParsed;
}
/**
* @param bool $bool
*
* @return $this
*/
public function setBlocksParsed(bool $bool)
{
$this->blocksParsed = $bool;
return $this;
}
public function getReferenceParser(): ReferenceParser
{
return $this->referenceParser;
}
}

View File

@@ -0,0 +1,99 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Reference\ReferenceParser;
interface ContextInterface
{
/**
* @return Document
*/
public function getDocument(): Document;
/**
* @return AbstractBlock|null
*/
public function getTip(): ?AbstractBlock;
/**
* @param AbstractBlock|null $block
*
* @return void
*/
public function setTip(?AbstractBlock $block);
/**
* @return int
*/
public function getLineNumber(): int;
/**
* @return string
*/
public function getLine(): string;
/**
* Finalize and close any unmatched blocks
*
* @return UnmatchedBlockCloser
*/
public function getBlockCloser(): UnmatchedBlockCloser;
/**
* @return AbstractBlock
*/
public function getContainer(): AbstractBlock;
/**
* @param AbstractBlock $container
*
* @return void
*/
public function setContainer(AbstractBlock $container);
/**
* @param AbstractBlock $block
*
* @return void
*/
public function addBlock(AbstractBlock $block);
/**
* @param AbstractBlock $replacement
*
* @return void
*/
public function replaceContainerBlock(AbstractBlock $replacement);
/**
* @return bool
*/
public function getBlocksParsed(): bool;
/**
* @param bool $bool
*
* @return $this
*/
public function setBlocksParsed(bool $bool);
/**
* @return ReferenceParser
*/
public function getReferenceParser(): ReferenceParser;
}

View File

@@ -0,0 +1,84 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
/**
* Converts CommonMark-compatible Markdown to HTML.
*
* @deprecated This class is deprecated since league/commonmark 1.4, use MarkdownConverter instead.
*/
class Converter implements ConverterInterface
{
/**
* The document parser instance.
*
* @var DocParserInterface
*/
protected $docParser;
/**
* The html renderer instance.
*
* @var ElementRendererInterface
*/
protected $htmlRenderer;
/**
* Create a new commonmark converter instance.
*
* @param DocParserInterface $docParser
* @param ElementRendererInterface $htmlRenderer
*/
public function __construct(DocParserInterface $docParser, ElementRendererInterface $htmlRenderer)
{
if (!($this instanceof MarkdownConverter)) {
@trigger_error(sprintf('The %s class is deprecated since league/commonmark 1.4, use %s instead.', self::class, MarkdownConverter::class), E_USER_DEPRECATED);
}
$this->docParser = $docParser;
$this->htmlRenderer = $htmlRenderer;
}
/**
* Converts CommonMark to HTML.
*
* @param string $commonMark
*
* @throws \RuntimeException
*
* @return string
*
* @api
*/
public function convertToHtml(string $commonMark): string
{
$documentAST = $this->docParser->parse($commonMark);
return $this->htmlRenderer->renderBlock($documentAST);
}
/**
* Converts CommonMark to HTML.
*
* @see Converter::convertToHtml
*
* @param string $commonMark
*
* @throws \RuntimeException
*
* @return string
*/
public function __invoke(string $commonMark): string
{
return $this->convertToHtml($commonMark);
}
}

View File

@@ -0,0 +1,21 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
/**
* Interface for a service which converts CommonMark to HTML.
*
* @deprecated ConverterInterface is deprecated since league/commonmark 1.4, use MarkdownConverterInterface instead
*/
interface ConverterInterface extends MarkdownConverterInterface
{
}

502
vendor/league/commonmark/src/Cursor.php vendored Normal file
View File

@@ -0,0 +1,502 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Exception\UnexpectedEncodingException;
class Cursor
{
public const INDENT_LEVEL = 4;
/**
* @var string
*/
private $line;
/**
* @var int
*/
private $length;
/**
* @var int
*
* It's possible for this to be 1 char past the end, meaning we've parsed all chars and have
* reached the end. In this state, any character-returning method MUST return null.
*/
private $currentPosition = 0;
/**
* @var int
*/
private $column = 0;
/**
* @var int
*/
private $indent = 0;
/**
* @var int
*/
private $previousPosition = 0;
/**
* @var int|null
*/
private $nextNonSpaceCache;
/**
* @var bool
*/
private $partiallyConsumedTab = false;
/**
* @var bool
*/
private $lineContainsTabs;
/**
* @var bool
*/
private $isMultibyte;
/**
* @var array<int, string>
*/
private $charCache = [];
/**
* @param string $line The line being parsed (ASCII or UTF-8)
*/
public function __construct(string $line)
{
if (!\mb_check_encoding($line, 'UTF-8')) {
throw new UnexpectedEncodingException('Unexpected encoding - UTF-8 or ASCII was expected');
}
$this->line = $line;
$this->length = \mb_strlen($line, 'UTF-8') ?: 0;
$this->isMultibyte = $this->length !== \strlen($line);
$this->lineContainsTabs = false !== \strpos($line, "\t");
}
/**
* Returns the position of the next character which is not a space (or tab)
*
* @return int
*/
public function getNextNonSpacePosition(): int
{
if ($this->nextNonSpaceCache !== null) {
return $this->nextNonSpaceCache;
}
$i = $this->currentPosition;
$cols = $this->column;
while (($c = $this->getCharacter($i)) !== null) {
if ($c === ' ') {
$i++;
$cols++;
} elseif ($c === "\t") {
$i++;
$cols += (4 - ($cols % 4));
} else {
break;
}
}
$nextNonSpace = ($c === null) ? $this->length : $i;
$this->indent = $cols - $this->column;
return $this->nextNonSpaceCache = $nextNonSpace;
}
/**
* Returns the next character which isn't a space (or tab)
*
* @return string
*/
public function getNextNonSpaceCharacter(): ?string
{
return $this->getCharacter($this->getNextNonSpacePosition());
}
/**
* Calculates the current indent (number of spaces after current position)
*
* @return int
*/
public function getIndent(): int
{
if ($this->nextNonSpaceCache === null) {
$this->getNextNonSpacePosition();
}
return $this->indent;
}
/**
* Whether the cursor is indented to INDENT_LEVEL
*
* @return bool
*/
public function isIndented(): bool
{
return $this->getIndent() >= self::INDENT_LEVEL;
}
/**
* @param int|null $index
*
* @return string|null
*/
public function getCharacter(?int $index = null): ?string
{
if ($index === null) {
$index = $this->currentPosition;
}
// Index out-of-bounds, or we're at the end
if ($index < 0 || $index >= $this->length) {
return null;
}
if ($this->isMultibyte) {
if (isset($this->charCache[$index])) {
return $this->charCache[$index];
}
return $this->charCache[$index] = \mb_substr($this->line, $index, 1, 'UTF-8');
}
return $this->line[$index];
}
/**
* Returns the next character (or null, if none) without advancing forwards
*
* @param int $offset
*
* @return string|null
*/
public function peek(int $offset = 1): ?string
{
return $this->getCharacter($this->currentPosition + $offset);
}
/**
* Whether the remainder is blank
*
* @return bool
*/
public function isBlank(): bool
{
return $this->nextNonSpaceCache === $this->length || $this->getNextNonSpacePosition() === $this->length;
}
/**
* Move the cursor forwards
*
* @return void
*/
public function advance()
{
$this->advanceBy(1);
}
/**
* Move the cursor forwards
*
* @param int $characters Number of characters to advance by
* @param bool $advanceByColumns Whether to advance by columns instead of spaces
*
* @return void
*/
public function advanceBy(int $characters, bool $advanceByColumns = false)
{
if ($characters === 0) {
$this->previousPosition = $this->currentPosition;
return;
}
$this->previousPosition = $this->currentPosition;
$this->nextNonSpaceCache = null;
// Optimization to avoid tab handling logic if we have no tabs
if (!$this->lineContainsTabs || false === \strpos(
$nextFewChars = $this->isMultibyte ?
\mb_substr($this->line, $this->currentPosition, $characters, 'UTF-8') :
\substr($this->line, $this->currentPosition, $characters),
"\t"
)) {
$length = \min($characters, $this->length - $this->currentPosition);
$this->partiallyConsumedTab = false;
$this->currentPosition += $length;
$this->column += $length;
return;
}
if ($characters === 1 && !empty($nextFewChars)) {
$asArray = [$nextFewChars];
} elseif ($this->isMultibyte) {
/** @var string[] $asArray */
$asArray = \preg_split('//u', $nextFewChars, -1, \PREG_SPLIT_NO_EMPTY);
} else {
$asArray = \str_split($nextFewChars);
}
foreach ($asArray as $relPos => $c) {
if ($c === "\t") {
$charsToTab = 4 - ($this->column % 4);
if ($advanceByColumns) {
$this->partiallyConsumedTab = $charsToTab > $characters;
$charsToAdvance = $charsToTab > $characters ? $characters : $charsToTab;
$this->column += $charsToAdvance;
$this->currentPosition += $this->partiallyConsumedTab ? 0 : 1;
$characters -= $charsToAdvance;
} else {
$this->partiallyConsumedTab = false;
$this->column += $charsToTab;
$this->currentPosition++;
$characters--;
}
} else {
$this->partiallyConsumedTab = false;
$this->currentPosition++;
$this->column++;
$characters--;
}
if ($characters <= 0) {
break;
}
}
}
/**
* Advances the cursor by a single space or tab, if present
*
* @return bool
*/
public function advanceBySpaceOrTab(): bool
{
$character = $this->getCharacter();
if ($character === ' ' || $character === "\t") {
$this->advanceBy(1, true);
return true;
}
return false;
}
/**
* Parse zero or more space/tab characters
*
* @return int Number of positions moved
*/
public function advanceToNextNonSpaceOrTab(): int
{
$newPosition = $this->getNextNonSpacePosition();
$this->advanceBy($newPosition - $this->currentPosition);
$this->partiallyConsumedTab = false;
return $this->currentPosition - $this->previousPosition;
}
/**
* Parse zero or more space characters, including at most one newline.
*
* Tab characters are not parsed with this function.
*
* @return int Number of positions moved
*/
public function advanceToNextNonSpaceOrNewline(): int
{
$remainder = $this->getRemainder();
// Optimization: Avoid the regex if we know there are no spaces or newlines
if (empty($remainder) || ($remainder[0] !== ' ' && $remainder[0] !== "\n")) {
$this->previousPosition = $this->currentPosition;
return 0;
}
$matches = [];
\preg_match('/^ *(?:\n *)?/', $remainder, $matches, \PREG_OFFSET_CAPTURE);
// [0][0] contains the matched text
// [0][1] contains the index of that match
$increment = $matches[0][1] + \strlen($matches[0][0]);
$this->advanceBy($increment);
return $this->currentPosition - $this->previousPosition;
}
/**
* Move the position to the very end of the line
*
* @return int The number of characters moved
*/
public function advanceToEnd(): int
{
$this->previousPosition = $this->currentPosition;
$this->nextNonSpaceCache = null;
$this->currentPosition = $this->length;
return $this->currentPosition - $this->previousPosition;
}
public function getRemainder(): string
{
if ($this->currentPosition >= $this->length) {
return '';
}
$prefix = '';
$position = $this->currentPosition;
if ($this->partiallyConsumedTab) {
$position++;
$charsToTab = 4 - ($this->column % 4);
$prefix = \str_repeat(' ', $charsToTab);
}
$subString = $this->isMultibyte ?
\mb_substr($this->line, $position, null, 'UTF-8') :
\substr($this->line, $position);
return $prefix . $subString;
}
public function getLine(): string
{
return $this->line;
}
public function isAtEnd(): bool
{
return $this->currentPosition >= $this->length;
}
/**
* Try to match a regular expression
*
* Returns the matching text and advances to the end of that match
*
* @param string $regex
*
* @return string|null
*/
public function match(string $regex): ?string
{
$subject = $this->getRemainder();
if (!\preg_match($regex, $subject, $matches, \PREG_OFFSET_CAPTURE)) {
return null;
}
// $matches[0][0] contains the matched text
// $matches[0][1] contains the index of that match
if ($this->isMultibyte) {
// PREG_OFFSET_CAPTURE always returns the byte offset, not the char offset, which is annoying
$offset = \mb_strlen(\substr($subject, 0, $matches[0][1]), 'UTF-8');
$matchLength = \mb_strlen($matches[0][0], 'UTF-8');
} else {
$offset = $matches[0][1];
$matchLength = \strlen($matches[0][0]);
}
// [0][0] contains the matched text
// [0][1] contains the index of that match
$this->advanceBy($offset + $matchLength);
return $matches[0][0];
}
/**
* Encapsulates the current state of this cursor in case you need to rollback later.
*
* WARNING: Do not parse or use the return value for ANYTHING except for
* passing it back into restoreState(), as the number of values and their
* contents may change in any future release without warning.
*
* @return array<mixed>
*/
public function saveState()
{
return [
$this->currentPosition,
$this->previousPosition,
$this->nextNonSpaceCache,
$this->indent,
$this->column,
$this->partiallyConsumedTab,
];
}
/**
* Restore the cursor to a previous state.
*
* Pass in the value previously obtained by calling saveState().
*
* @param array<mixed> $state
*
* @return void
*/
public function restoreState($state)
{
list(
$this->currentPosition,
$this->previousPosition,
$this->nextNonSpaceCache,
$this->indent,
$this->column,
$this->partiallyConsumedTab,
) = $state;
}
public function getPosition(): int
{
return $this->currentPosition;
}
public function getPreviousText(): string
{
return \mb_substr($this->line, $this->previousPosition, $this->currentPosition - $this->previousPosition, 'UTF-8');
}
public function getSubstring(int $start, ?int $length = null): string
{
if ($this->isMultibyte) {
return \mb_substr($this->line, $start, $length, 'UTF-8');
} elseif ($length !== null) {
return \substr($this->line, $start, $length);
}
return \substr($this->line, $start);
}
public function getColumn(): int
{
return $this->column;
}
}

View File

@@ -0,0 +1,152 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter;
use League\CommonMark\Inline\Element\AbstractStringContainer;
final class Delimiter implements DelimiterInterface
{
/** @var string */
private $char;
/** @var int */
private $length;
/** @var int */
private $originalLength;
/** @var AbstractStringContainer */
private $inlineNode;
/** @var DelimiterInterface|null */
private $previous;
/** @var DelimiterInterface|null */
private $next;
/** @var bool */
private $canOpen;
/** @var bool */
private $canClose;
/** @var bool */
private $active;
/** @var int|null */
private $index;
/**
* @param string $char
* @param int $numDelims
* @param AbstractStringContainer $node
* @param bool $canOpen
* @param bool $canClose
* @param int|null $index
*/
public function __construct(string $char, int $numDelims, AbstractStringContainer $node, bool $canOpen, bool $canClose, ?int $index = null)
{
$this->char = $char;
$this->length = $numDelims;
$this->originalLength = $numDelims;
$this->inlineNode = $node;
$this->canOpen = $canOpen;
$this->canClose = $canClose;
$this->active = true;
$this->index = $index;
}
public function canClose(): bool
{
return $this->canClose;
}
/**
* @param bool $canClose
*
* @return void
*/
public function setCanClose(bool $canClose)
{
$this->canClose = $canClose;
}
public function canOpen(): bool
{
return $this->canOpen;
}
public function isActive(): bool
{
return $this->active;
}
public function setActive(bool $active)
{
$this->active = $active;
}
public function getChar(): string
{
return $this->char;
}
public function getIndex(): ?int
{
return $this->index;
}
public function getNext(): ?DelimiterInterface
{
return $this->next;
}
public function setNext(?DelimiterInterface $next)
{
$this->next = $next;
}
public function getLength(): int
{
return $this->length;
}
public function setLength(int $length)
{
$this->length = $length;
}
public function getOriginalLength(): int
{
return $this->originalLength;
}
public function getInlineNode(): AbstractStringContainer
{
return $this->inlineNode;
}
public function getPrevious(): ?DelimiterInterface
{
return $this->previous;
}
public function setPrevious(?DelimiterInterface $previous): DelimiterInterface
{
$this->previous = $previous;
return $this;
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter;
use League\CommonMark\Inline\Element\AbstractStringContainer;
interface DelimiterInterface
{
public function canClose(): bool;
public function canOpen(): bool;
public function isActive(): bool;
/**
* @param bool $active
*
* @return void
*/
public function setActive(bool $active);
/**
* @return string
*/
public function getChar(): string;
public function getIndex(): ?int;
public function getNext(): ?DelimiterInterface;
/**
* @param DelimiterInterface|null $next
*
* @return void
*/
public function setNext(?DelimiterInterface $next);
public function getLength(): int;
/**
* @param int $length
*
* @return void
*/
public function setLength(int $length);
public function getOriginalLength(): int;
public function getInlineNode(): AbstractStringContainer;
public function getPrevious(): ?DelimiterInterface;
/**
* @param DelimiterInterface|null $previous
*
* @return mixed|void
*/
public function setPrevious(?DelimiterInterface $previous);
}

View File

@@ -0,0 +1,234 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Inline\AdjacentTextMerger;
final class DelimiterStack
{
/**
* @var DelimiterInterface|null
*/
private $top;
/**
* @param DelimiterInterface $newDelimiter
*
* @return void
*/
public function push(DelimiterInterface $newDelimiter)
{
$newDelimiter->setPrevious($this->top);
if ($this->top !== null) {
$this->top->setNext($newDelimiter);
}
$this->top = $newDelimiter;
}
private function findEarliest(DelimiterInterface $stackBottom = null): ?DelimiterInterface
{
$delimiter = $this->top;
while ($delimiter !== null && $delimiter->getPrevious() !== $stackBottom) {
$delimiter = $delimiter->getPrevious();
}
return $delimiter;
}
/**
* @param DelimiterInterface $delimiter
*
* @return void
*/
public function removeDelimiter(DelimiterInterface $delimiter)
{
if ($delimiter->getPrevious() !== null) {
$delimiter->getPrevious()->setNext($delimiter->getNext());
}
if ($delimiter->getNext() === null) {
// top of stack
$this->top = $delimiter->getPrevious();
} else {
$delimiter->getNext()->setPrevious($delimiter->getPrevious());
}
}
private function removeDelimiterAndNode(DelimiterInterface $delimiter): void
{
$delimiter->getInlineNode()->detach();
$this->removeDelimiter($delimiter);
}
private function removeDelimitersBetween(DelimiterInterface $opener, DelimiterInterface $closer): void
{
$delimiter = $closer->getPrevious();
while ($delimiter !== null && $delimiter !== $opener) {
$previous = $delimiter->getPrevious();
$this->removeDelimiter($delimiter);
$delimiter = $previous;
}
}
/**
* @param DelimiterInterface|null $stackBottom
*
* @return void
*/
public function removeAll(DelimiterInterface $stackBottom = null)
{
while ($this->top && $this->top !== $stackBottom) {
$this->removeDelimiter($this->top);
}
}
/**
* @param string $character
*
* @return void
*/
public function removeEarlierMatches(string $character)
{
$opener = $this->top;
while ($opener !== null) {
if ($opener->getChar() === $character) {
$opener->setActive(false);
}
$opener = $opener->getPrevious();
}
}
/**
* @param string|string[] $characters
*
* @return DelimiterInterface|null
*/
public function searchByCharacter($characters): ?DelimiterInterface
{
if (!\is_array($characters)) {
$characters = [$characters];
}
$opener = $this->top;
while ($opener !== null) {
if (\in_array($opener->getChar(), $characters)) {
break;
}
$opener = $opener->getPrevious();
}
return $opener;
}
/**
* @param DelimiterInterface|null $stackBottom
* @param DelimiterProcessorCollection $processors
*
* @return void
*/
public function processDelimiters(?DelimiterInterface $stackBottom, DelimiterProcessorCollection $processors)
{
$openersBottom = [];
// Find first closer above stackBottom
$closer = $this->findEarliest($stackBottom);
// Move forward, looking for closers, and handling each
while ($closer !== null) {
$delimiterChar = $closer->getChar();
$delimiterProcessor = $processors->getDelimiterProcessor($delimiterChar);
if (!$closer->canClose() || $delimiterProcessor === null) {
$closer = $closer->getNext();
continue;
}
$openingDelimiterChar = $delimiterProcessor->getOpeningCharacter();
$useDelims = 0;
$openerFound = false;
$potentialOpenerFound = false;
$opener = $closer->getPrevious();
while ($opener !== null && $opener !== $stackBottom && $opener !== ($openersBottom[$delimiterChar] ?? null)) {
if ($opener->canOpen() && $opener->getChar() === $openingDelimiterChar) {
$potentialOpenerFound = true;
$useDelims = $delimiterProcessor->getDelimiterUse($opener, $closer);
if ($useDelims > 0) {
$openerFound = true;
break;
}
}
$opener = $opener->getPrevious();
}
if (!$openerFound) {
if (!$potentialOpenerFound) {
// Only do this when we didn't even have a potential
// opener (one that matches the character and can open).
// If an opener was rejected because of the number of
// delimiters (e.g. because of the "multiple of 3"
// Set lower bound for future searches for openersrule),
// we want to consider it next time because the number
// of delimiters can change as we continue processing.
$openersBottom[$delimiterChar] = $closer->getPrevious();
if (!$closer->canOpen()) {
// We can remove a closer that can't be an opener,
// once we've seen there's no matching opener.
$this->removeDelimiter($closer);
}
}
$closer = $closer->getNext();
continue;
}
$openerNode = $opener->getInlineNode();
$closerNode = $closer->getInlineNode();
// Remove number of used delimiters from stack and inline nodes.
$opener->setLength($opener->getLength() - $useDelims);
$closer->setLength($closer->getLength() - $useDelims);
$openerNode->setContent(\substr($openerNode->getContent(), 0, -$useDelims));
$closerNode->setContent(\substr($closerNode->getContent(), 0, -$useDelims));
$this->removeDelimitersBetween($opener, $closer);
// The delimiter processor can re-parent the nodes between opener and closer,
// so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves.
AdjacentTextMerger::mergeTextNodesBetweenExclusive($openerNode, $closerNode);
$delimiterProcessor->process($openerNode, $closerNode, $useDelims);
// No delimiter characters left to process, so we can remove delimiter and the now empty node.
if ($opener->getLength() === 0) {
$this->removeDelimiterAndNode($opener);
}
if ($closer->getLength() === 0) {
$next = $closer->getNext();
$this->removeDelimiterAndNode($closer);
$closer = $next;
}
}
// Remove all delimiters
$this->removeAll($stackBottom);
}
}

View File

@@ -0,0 +1,73 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
final class DelimiterProcessorCollection implements DelimiterProcessorCollectionInterface
{
/** @var array<string,DelimiterProcessorInterface>|DelimiterProcessorInterface[] */
private $processorsByChar = [];
public function add(DelimiterProcessorInterface $processor)
{
$opening = $processor->getOpeningCharacter();
$closing = $processor->getClosingCharacter();
if ($opening === $closing) {
$old = $this->processorsByChar[$opening] ?? null;
if ($old !== null && $old->getOpeningCharacter() === $old->getClosingCharacter()) {
$this->addStaggeredDelimiterProcessorForChar($opening, $old, $processor);
} else {
$this->addDelimiterProcessorForChar($opening, $processor);
}
} else {
$this->addDelimiterProcessorForChar($opening, $processor);
$this->addDelimiterProcessorForChar($closing, $processor);
}
}
public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface
{
return $this->processorsByChar[$char] ?? null;
}
public function getDelimiterCharacters(): array
{
return \array_keys($this->processorsByChar);
}
private function addDelimiterProcessorForChar(string $delimiterChar, DelimiterProcessorInterface $processor): void
{
if (isset($this->processorsByChar[$delimiterChar])) {
throw new \InvalidArgumentException(\sprintf('Delim processor for character "%s" already exists', $processor->getOpeningCharacter()));
}
$this->processorsByChar[$delimiterChar] = $processor;
}
private function addStaggeredDelimiterProcessorForChar(string $opening, DelimiterProcessorInterface $old, DelimiterProcessorInterface $new): void
{
if ($old instanceof StaggeredDelimiterProcessor) {
$s = $old;
} else {
$s = new StaggeredDelimiterProcessor($opening, $old);
}
$s->add($new);
$this->processorsByChar[$opening] = $s;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
interface DelimiterProcessorCollectionInterface
{
/**
* Add the given delim processor to the collection
*
* @param DelimiterProcessorInterface $processor The delim processor to add
*
* @throws \InvalidArgumentException Exception will be thrown if attempting to add multiple processors for the same character
*
* @return void
*/
public function add(DelimiterProcessorInterface $processor);
/**
* Returns the delim processor which handles the given character if one exists
*
* @param string $char
*
* @return DelimiterProcessorInterface|null
*/
public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface;
/**
* Returns an array of delimiter characters who have associated processors
*
* @return string[]
*/
public function getDelimiterCharacters(): array;
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Inline\Element\AbstractStringContainer;
/**
* Interface for a delimiter processor
*/
interface DelimiterProcessorInterface
{
/**
* Returns the character that marks the beginning of a delimited node.
*
* This must not clash with any other processors being added to the environment.
*
* @return string
*/
public function getOpeningCharacter(): string;
/**
* Returns the character that marks the ending of a delimited node.
*
* This must not clash with any other processors being added to the environment.
*
* Note that for a symmetric delimiter such as "*", this is the same as the opening.
*
* @return string
*/
public function getClosingCharacter(): string;
/**
* Minimum number of delimiter characters that are needed to active this.
*
* Must be at least 1.
*
* @return int
*/
public function getMinLength(): int;
/**
* Determine how many (if any) of the delimiter characters should be used.
*
* This allows implementations to decide how many characters to be used
* based on the properties of the delimiter runs. An implementation can also
* return 0 when it doesn't want to allow this particular combination of
* delimiter runs.
*
* @param DelimiterInterface $opener The opening delimiter run
* @param DelimiterInterface $closer The closing delimiter run
*
* @return int
*/
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
/**
* Process the matched delimiters, e.g. by wrapping the nodes between opener
* and closer in a new node, or appending a new node after the opener.
*
* Note that removal of the delimiter from the delimiter nodes and detaching
* them is done by the caller.
*
* @param AbstractStringContainer $opener The node that contained the opening delimiter
* @param AbstractStringContainer $closer The node that contained the closing delimiter
* @param int $delimiterUse The number of delimiters that were used
*
* @return void
*/
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse);
}

View File

@@ -0,0 +1,137 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Inline\Element\AbstractStringContainer;
use League\CommonMark\Inline\Element\Emphasis;
use League\CommonMark\Inline\Element\Strong;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, ConfigurationAwareInterface
{
/** @var string */
private $char;
/** @var ConfigurationInterface|null */
private $config;
/**
* @param string $char The emphasis character to use (typically '*' or '_')
*/
public function __construct(string $char)
{
$this->char = $char;
}
public function getOpeningCharacter(): string
{
return $this->char;
}
public function getClosingCharacter(): string
{
return $this->char;
}
public function getMinLength(): int
{
return 1;
}
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
{
// "Multiple of 3" rule for internal delimiter runs
if (($opener->canClose() || $closer->canOpen()) && $closer->getOriginalLength() % 3 !== 0 && ($opener->getOriginalLength() + $closer->getOriginalLength()) % 3 === 0) {
return 0;
}
// Calculate actual number of delimiters used from this closer
if ($opener->getLength() >= 2 && $closer->getLength() >= 2) {
if ($this->enableStrong()) {
return 2;
}
return 0;
}
if ($this->enableEm()) {
return 1;
}
return 0;
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse)
{
if ($delimiterUse === 1) {
$emphasis = new Emphasis();
} elseif ($delimiterUse === 2) {
$emphasis = new Strong();
} else {
return;
}
$next = $opener->next();
while ($next !== null && $next !== $closer) {
$tmp = $next->next();
$emphasis->appendChild($next);
$next = $tmp;
}
$opener->insertAfter($emphasis);
}
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
private function enableStrong(): bool
{
if ($this->config === null) {
return false;
}
$deprecatedEnableStrong = $this->config->get('enable_strong', ConfigurationInterface::MISSING);
if ($deprecatedEnableStrong !== ConfigurationInterface::MISSING) {
@\trigger_error('The "enable_strong" configuration option is deprecated in league/commonmark 1.6 and will be replaced with "commonmark > enable_strong" in 2.0', \E_USER_DEPRECATED);
} else {
$deprecatedEnableStrong = true;
}
return $this->config->get('commonmark/enable_strong', $deprecatedEnableStrong);
}
private function enableEm(): bool
{
if ($this->config === null) {
return false;
}
$deprecatedEnableEm = $this->config->get('enable_em', ConfigurationInterface::MISSING);
if ($deprecatedEnableEm !== ConfigurationInterface::MISSING) {
@\trigger_error('The "enable_em" configuration option is deprecated in league/commonmark 1.6 and will be replaced with "commonmark > enable_em" in 2.0', \E_USER_DEPRECATED);
} else {
$deprecatedEnableEm = true;
}
return $this->config->get('commonmark/enable_em', $deprecatedEnableEm);
}
}

View File

@@ -0,0 +1,106 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Additional emphasis processing code based on commonmark-java (https://github.com/atlassian/commonmark-java)
* - (c) Atlassian Pty Ltd
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Inline\Element\AbstractStringContainer;
/**
* An implementation of DelimiterProcessorInterface that dispatches all calls to two or more other DelimiterProcessors
* depending on the length of the delimiter run. All child DelimiterProcessors must have different minimum
* lengths. A given delimiter run is dispatched to the child with the largest acceptable minimum length. If no
* child is applicable, the one with the largest minimum length is chosen.
*
* @internal
*/
final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
{
/** @var string */
private $delimiterChar;
/** @var int */
private $minLength = 0;
/** @var array<int, DelimiterProcessorInterface>|DelimiterProcessorInterface[] */
private $processors = []; // keyed by minLength in reverse order
public function __construct(string $char, DelimiterProcessorInterface $processor)
{
$this->delimiterChar = $char;
$this->add($processor);
}
public function getOpeningCharacter(): string
{
return $this->delimiterChar;
}
public function getClosingCharacter(): string
{
return $this->delimiterChar;
}
public function getMinLength(): int
{
return $this->minLength;
}
/**
* Adds the given processor to this staggered delimiter processor
*
* @param DelimiterProcessorInterface $processor
*
* @return void
*/
public function add(DelimiterProcessorInterface $processor)
{
$len = $processor->getMinLength();
if (isset($this->processors[$len])) {
throw new \InvalidArgumentException(\sprintf('Cannot add two delimiter processors for char "%s" and minimum length %d', $this->delimiterChar, $len));
}
$this->processors[$len] = $processor;
\krsort($this->processors);
$this->minLength = \min($this->minLength, $len);
}
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int
{
return $this->findProcessor($opener->getLength())->getDelimiterUse($opener, $closer);
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse)
{
$this->findProcessor($delimiterUse)->process($opener, $closer, $delimiterUse);
}
private function findProcessor(int $len): DelimiterProcessorInterface
{
// Find the "longest" processor which can handle this length
foreach ($this->processors as $processor) {
if ($processor->getMinLength() <= $len) {
return $processor;
}
}
// Just use the first one in our list
/** @var DelimiterProcessorInterface $first */
$first = \reset($this->processors);
return $first;
}
}

View File

@@ -0,0 +1,237 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\AbstractStringContainerBlock;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\Block\Element\StringContainerInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Event\DocumentPreParsedEvent;
use League\CommonMark\Input\MarkdownInput;
final class DocParser implements DocParserInterface
{
/**
* @var EnvironmentInterface
*/
private $environment;
/**
* @var InlineParserEngine
*/
private $inlineParserEngine;
/**
* @var int|float
*/
private $maxNestingLevel;
/**
* @param EnvironmentInterface $environment
*/
public function __construct(EnvironmentInterface $environment)
{
$this->environment = $environment;
$this->inlineParserEngine = new InlineParserEngine($environment);
$this->maxNestingLevel = $environment->getConfig('max_nesting_level', \PHP_INT_MAX);
if (\is_float($this->maxNestingLevel)) {
if ($this->maxNestingLevel === \INF) {
@\trigger_error('Using the "INF" constant for the "max_nesting_level" configuration option is deprecated in league/commonmark 1.6 and will not be allowed in 2.0; use "PHP_INT_MAX" instead', \E_USER_DEPRECATED);
} else {
@\trigger_error('Using a float for the "max_nesting_level" configuration option is deprecated in league/commonmark 1.6 and will not be allowed in 2.0', \E_USER_DEPRECATED);
}
}
}
/**
* @param string $input
*
* @throws \RuntimeException
*
* @return Document
*/
public function parse(string $input): Document
{
$document = new Document();
$preParsedEvent = new DocumentPreParsedEvent($document, new MarkdownInput($input));
$this->environment->dispatch($preParsedEvent);
$markdown = $preParsedEvent->getMarkdown();
$context = new Context($document, $this->environment);
foreach ($markdown->getLines() as $line) {
$context->setNextLine($line);
$this->incorporateLine($context);
}
$lineCount = $markdown->getLineCount();
while ($tip = $context->getTip()) {
$tip->finalize($context, $lineCount);
}
$this->processInlines($context);
$this->environment->dispatch(new DocumentParsedEvent($document));
return $document;
}
private function incorporateLine(ContextInterface $context): void
{
$context->getBlockCloser()->resetTip();
$context->setBlocksParsed(false);
$cursor = new Cursor($context->getLine());
$this->resetContainer($context, $cursor);
$context->getBlockCloser()->setLastMatchedContainer($context->getContainer());
$this->parseBlocks($context, $cursor);
// What remains at the offset is a text line. Add the text to the appropriate container.
// First check for a lazy paragraph continuation:
if ($this->handleLazyParagraphContinuation($context, $cursor)) {
return;
}
// not a lazy continuation
// finalize any blocks not matched
$context->getBlockCloser()->closeUnmatchedBlocks();
// Determine whether the last line is blank, updating parents as needed
$this->setAndPropagateLastLineBlank($context, $cursor);
// Handle any remaining cursor contents
if ($context->getContainer() instanceof StringContainerInterface) {
$context->getContainer()->handleRemainingContents($context, $cursor);
} elseif (!$cursor->isBlank()) {
// Create paragraph container for line
$p = new Paragraph();
$context->addBlock($p);
$cursor->advanceToNextNonSpaceOrTab();
$p->addLine($cursor->getRemainder());
}
}
private function processInlines(ContextInterface $context): void
{
$walker = $context->getDocument()->walker();
while ($event = $walker->next()) {
if (!$event->isEntering()) {
continue;
}
$node = $event->getNode();
if ($node instanceof AbstractStringContainerBlock) {
$this->inlineParserEngine->parse($node, $context->getDocument()->getReferenceMap());
}
}
}
/**
* Sets the container to the last open child (or its parent)
*
* @param ContextInterface $context
* @param Cursor $cursor
*/
private function resetContainer(ContextInterface $context, Cursor $cursor): void
{
$container = $context->getDocument();
while ($lastChild = $container->lastChild()) {
if (!($lastChild instanceof AbstractBlock)) {
break;
}
if (!$lastChild->isOpen()) {
break;
}
$container = $lastChild;
if (!$container->matchesNextLine($cursor)) {
$container = $container->parent(); // back up to the last matching block
break;
}
}
$context->setContainer($container);
}
/**
* Parse blocks
*
* @param ContextInterface $context
* @param Cursor $cursor
*/
private function parseBlocks(ContextInterface $context, Cursor $cursor): void
{
while (!$context->getContainer()->isCode() && !$context->getBlocksParsed()) {
$parsed = false;
foreach ($this->environment->getBlockParsers() as $parser) {
if ($parser->parse($context, $cursor)) {
$parsed = true;
break;
}
}
if (!$parsed || $context->getContainer() instanceof StringContainerInterface || (($tip = $context->getTip()) && $tip->getDepth() >= $this->maxNestingLevel)) {
$context->setBlocksParsed(true);
break;
}
}
}
private function handleLazyParagraphContinuation(ContextInterface $context, Cursor $cursor): bool
{
$tip = $context->getTip();
if ($tip instanceof Paragraph &&
!$context->getBlockCloser()->areAllClosed() &&
!$cursor->isBlank() &&
\count($tip->getStrings()) > 0) {
// lazy paragraph continuation
$tip->addLine($cursor->getRemainder());
return true;
}
return false;
}
private function setAndPropagateLastLineBlank(ContextInterface $context, Cursor $cursor): void
{
$container = $context->getContainer();
if ($cursor->isBlank() && $lastChild = $container->lastChild()) {
if ($lastChild instanceof AbstractBlock) {
$lastChild->setLastLineBlank(true);
}
}
$lastLineBlank = $container->shouldLastLineBeBlank($cursor, $context->getLineNumber());
// Propagate lastLineBlank up through parents:
while ($container instanceof AbstractBlock && $container->endsWithBlankLine() !== $lastLineBlank) {
$container->setLastLineBlank($lastLineBlank);
$container = $container->parent();
}
}
}

View File

@@ -0,0 +1,26 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Element\Document;
interface DocParserInterface
{
/**
* @param string $input
*
* @throws \RuntimeException
*
* @return Document
*/
public function parse(string $input): Document;
}

View File

@@ -0,0 +1,64 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Inline\Element\AbstractInline;
/**
* Renders a parsed AST to a string representation
*/
interface ElementRendererInterface
{
/**
* @param string $option
* @param mixed $default
*
* @return mixed|null
*/
public function getOption(string $option, $default = null);
/**
* @param AbstractInline $inline
*
* @return string
*/
public function renderInline(AbstractInline $inline): string;
/**
* @param AbstractInline[] $inlines
*
* @return string
*/
public function renderInlines(iterable $inlines): string;
/**
* @param AbstractBlock $block
* @param bool $inTightList
*
* @throws \RuntimeException
*
* @return string
*/
public function renderBlock(AbstractBlock $block, bool $inTightList = false): string;
/**
* @param AbstractBlock[] $blocks
* @param bool $inTightList
*
* @return string
*/
public function renderBlocks(iterable $blocks, bool $inTightList = false): string;
}

View File

@@ -0,0 +1,435 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Event\AbstractEvent;
use League\CommonMark\Extension\CommonMarkCoreExtension;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\Configuration;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\PrioritizedList;
final class Environment implements ConfigurableEnvironmentInterface
{
/**
* @var ExtensionInterface[]
*/
private $extensions = [];
/**
* @var ExtensionInterface[]
*/
private $uninitializedExtensions = [];
/**
* @var bool
*/
private $extensionsInitialized = false;
/**
* @var PrioritizedList<BlockParserInterface>
*/
private $blockParsers;
/**
* @var PrioritizedList<InlineParserInterface>
*/
private $inlineParsers;
/**
* @var array<string, PrioritizedList<InlineParserInterface>>
*/
private $inlineParsersByCharacter = [];
/**
* @var DelimiterProcessorCollection
*/
private $delimiterProcessors;
/**
* @var array<string, PrioritizedList<BlockRendererInterface>>
*/
private $blockRenderersByClass = [];
/**
* @var array<string, PrioritizedList<InlineRendererInterface>>
*/
private $inlineRenderersByClass = [];
/**
* @var array<string, PrioritizedList<callable>>
*/
private $listeners = [];
/**
* @var Configuration
*/
private $config;
/**
* @var string
*/
private $inlineParserCharacterRegex;
/**
* @param array<string, mixed> $config
*/
public function __construct(array $config = [])
{
$this->config = new Configuration($config);
$this->blockParsers = new PrioritizedList();
$this->inlineParsers = new PrioritizedList();
$this->delimiterProcessors = new DelimiterProcessorCollection();
}
public function mergeConfig(array $config = [])
{
if (\func_num_args() === 0) {
@\trigger_error('Calling Environment::mergeConfig() without any parameters is deprecated in league/commonmark 1.6 and will not be allowed in 2.0', \E_USER_DEPRECATED);
}
$this->assertUninitialized('Failed to modify configuration.');
$this->config->merge($config);
}
public function setConfig(array $config = [])
{
@\trigger_error('The Environment::setConfig() method is deprecated in league/commonmark 1.6 and will be removed in 2.0. Use mergeConfig() instead.', \E_USER_DEPRECATED);
$this->assertUninitialized('Failed to modify configuration.');
$this->config->replace($config);
}
public function getConfig($key = null, $default = null)
{
return $this->config->get($key, $default);
}
public function addBlockParser(BlockParserInterface $parser, int $priority = 0): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add block parser.');
$this->blockParsers->add($parser, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($parser);
return $this;
}
public function addInlineParser(InlineParserInterface $parser, int $priority = 0): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add inline parser.');
$this->inlineParsers->add($parser, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($parser);
foreach ($parser->getCharacters() as $character) {
if (!isset($this->inlineParsersByCharacter[$character])) {
$this->inlineParsersByCharacter[$character] = new PrioritizedList();
}
$this->inlineParsersByCharacter[$character]->add($parser, $priority);
}
return $this;
}
public function addDelimiterProcessor(DelimiterProcessorInterface $processor): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add delimiter processor.');
$this->delimiterProcessors->add($processor);
$this->injectEnvironmentAndConfigurationIfNeeded($processor);
return $this;
}
public function addBlockRenderer($blockClass, BlockRendererInterface $blockRenderer, int $priority = 0): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add block renderer.');
if (!isset($this->blockRenderersByClass[$blockClass])) {
$this->blockRenderersByClass[$blockClass] = new PrioritizedList();
}
$this->blockRenderersByClass[$blockClass]->add($blockRenderer, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($blockRenderer);
return $this;
}
public function addInlineRenderer(string $inlineClass, InlineRendererInterface $renderer, int $priority = 0): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add inline renderer.');
if (!isset($this->inlineRenderersByClass[$inlineClass])) {
$this->inlineRenderersByClass[$inlineClass] = new PrioritizedList();
}
$this->inlineRenderersByClass[$inlineClass]->add($renderer, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($renderer);
return $this;
}
public function getBlockParsers(): iterable
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->blockParsers->getIterator();
}
public function getInlineParsersForCharacter(string $character): iterable
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
if (!isset($this->inlineParsersByCharacter[$character])) {
return [];
}
return $this->inlineParsersByCharacter[$character]->getIterator();
}
public function getDelimiterProcessors(): DelimiterProcessorCollection
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->delimiterProcessors;
}
public function getBlockRenderersForClass(string $blockClass): iterable
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->getRenderersByClass($this->blockRenderersByClass, $blockClass, BlockRendererInterface::class);
}
public function getInlineRenderersForClass(string $inlineClass): iterable
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->getRenderersByClass($this->inlineRenderersByClass, $inlineClass, InlineRendererInterface::class);
}
/**
* Get all registered extensions
*
* @return ExtensionInterface[]
*/
public function getExtensions(): iterable
{
return $this->extensions;
}
/**
* Add a single extension
*
* @param ExtensionInterface $extension
*
* @return $this
*/
public function addExtension(ExtensionInterface $extension): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add extension.');
$this->extensions[] = $extension;
$this->uninitializedExtensions[] = $extension;
return $this;
}
private function initializeExtensions(): void
{
// Ask all extensions to register their components
while (!empty($this->uninitializedExtensions)) {
foreach ($this->uninitializedExtensions as $i => $extension) {
$extension->register($this);
unset($this->uninitializedExtensions[$i]);
}
}
$this->extensionsInitialized = true;
// Lastly, let's build a regex which matches non-inline characters
// This will enable a huge performance boost with inline parsing
$this->buildInlineParserCharacterRegex();
}
/**
* @param object $object
*/
private function injectEnvironmentAndConfigurationIfNeeded($object): void
{
if ($object instanceof EnvironmentAwareInterface) {
$object->setEnvironment($this);
}
if ($object instanceof ConfigurationAwareInterface) {
$object->setConfiguration($this->config);
}
}
public static function createCommonMarkEnvironment(): ConfigurableEnvironmentInterface
{
$environment = new static();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->mergeConfig([
'renderer' => [
'block_separator' => "\n",
'inner_separator' => "\n",
'soft_break' => "\n",
],
'html_input' => self::HTML_INPUT_ALLOW,
'allow_unsafe_links' => true,
'max_nesting_level' => \PHP_INT_MAX,
]);
return $environment;
}
public static function createGFMEnvironment(): ConfigurableEnvironmentInterface
{
$environment = self::createCommonMarkEnvironment();
$environment->addExtension(new GithubFlavoredMarkdownExtension());
return $environment;
}
public function getInlineParserCharacterRegex(): string
{
return $this->inlineParserCharacterRegex;
}
public function addEventListener(string $eventClass, callable $listener, int $priority = 0): ConfigurableEnvironmentInterface
{
$this->assertUninitialized('Failed to add event listener.');
if (!isset($this->listeners[$eventClass])) {
$this->listeners[$eventClass] = new PrioritizedList();
}
$this->listeners[$eventClass]->add($listener, $priority);
if (\is_object($listener)) {
$this->injectEnvironmentAndConfigurationIfNeeded($listener);
} elseif (\is_array($listener) && \is_object($listener[0])) {
$this->injectEnvironmentAndConfigurationIfNeeded($listener[0]);
}
return $this;
}
public function dispatch(AbstractEvent $event): void
{
if (!$this->extensionsInitialized) {
$this->initializeExtensions();
}
$type = \get_class($event);
foreach ($this->listeners[$type] ?? [] as $listener) {
if ($event->isPropagationStopped()) {
return;
}
$listener($event);
}
}
private function buildInlineParserCharacterRegex(): void
{
$chars = \array_unique(\array_merge(
\array_keys($this->inlineParsersByCharacter),
$this->delimiterProcessors->getDelimiterCharacters()
));
if (empty($chars)) {
// If no special inline characters exist then parse the whole line
$this->inlineParserCharacterRegex = '/^.+$/';
} else {
// Match any character which inline parsers are not interested in
$this->inlineParserCharacterRegex = '/^[^' . \preg_quote(\implode('', $chars), '/') . ']+/';
// Only add the u modifier (which slows down performance) if we have a multi-byte UTF-8 character in our regex
if (\strlen($this->inlineParserCharacterRegex) > \mb_strlen($this->inlineParserCharacterRegex)) {
$this->inlineParserCharacterRegex .= 'u';
}
}
}
/**
* @param string $message
*
* @throws \RuntimeException
*/
private function assertUninitialized(string $message): void
{
if ($this->extensionsInitialized) {
throw new \RuntimeException($message . ' Extensions have already been initialized.');
}
}
/**
* @param array<string, PrioritizedList> $list
* @param string $class
* @param string $type
*
* @return iterable
*
* @phpstan-template T
*
* @phpstan-param array<string, PrioritizedList<T>> $list
* @phpstan-param string $class
* @phpstan-param class-string<T> $type
*
* @phpstan-return iterable<T>
*/
private function getRenderersByClass(array &$list, string $class, string $type): iterable
{
// If renderers are defined for this specific class, return them immediately
if (isset($list[$class])) {
return $list[$class];
}
while (\class_exists($parent = $parent ?? $class) && $parent = \get_parent_class($parent)) {
if (!isset($list[$parent])) {
continue;
}
// "Cache" this result to avoid future loops
return $list[$class] = $list[$parent];
}
return [];
}
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
interface EnvironmentAwareInterface
{
/**
* @param EnvironmentInterface $environment
*
* @return void
*/
public function setEnvironment(EnvironmentInterface $environment);
}

View File

@@ -0,0 +1,83 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Event\AbstractEvent;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
interface EnvironmentInterface
{
const HTML_INPUT_STRIP = 'strip';
const HTML_INPUT_ALLOW = 'allow';
const HTML_INPUT_ESCAPE = 'escape';
/**
* @param string|null $key
* @param mixed $default
*
* @return mixed
*/
public function getConfig($key = null, $default = null);
/**
* @return iterable<BlockParserInterface>
*/
public function getBlockParsers(): iterable;
/**
* @param string $character
*
* @return iterable<InlineParserInterface>
*/
public function getInlineParsersForCharacter(string $character): iterable;
/**
* @return DelimiterProcessorCollection
*/
public function getDelimiterProcessors(): DelimiterProcessorCollection;
/**
* @param string $blockClass
*
* @return iterable<BlockRendererInterface>
*/
public function getBlockRenderersForClass(string $blockClass): iterable;
/**
* @param string $inlineClass
*
* @return iterable<InlineRendererInterface>
*/
public function getInlineRenderersForClass(string $inlineClass): iterable;
/**
* Regex which matches any character which doesn't indicate an inline element
*
* This allows us to parse multiple non-special characters at once
*
* @return string
*/
public function getInlineParserCharacterRegex(): string;
/**
* Dispatches the given event to listeners
*
* @param AbstractEvent $event
*
* @return void
*/
public function dispatch(AbstractEvent $event): void;
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the Symfony EventDispatcher "Event" contract
* - (c) 2018-2019 Fabien Potencier
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Event;
/**
* Base class for classes containing event data.
*
* This class contains no event data. It is used by events that do not pass
* state information to an event handler when an event is raised.
*
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*/
abstract class AbstractEvent
{
/** @var bool */
private $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.
*/
final public function isPropagationStopped(): bool
{
return $this->propagationStopped;
}
/**
* Stops the propagation of the event to further event listeners.
*
* If multiple event listeners are connected to the same event, no
* further event listener will be triggered once any trigger calls
* stopPropagation().
*/
final public function stopPropagation(): void
{
$this->propagationStopped = true;
}
}

View File

@@ -0,0 +1,33 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Event;
use League\CommonMark\Block\Element\Document;
/**
* Event dispatched when the document has been fully parsed
*/
final class DocumentParsedEvent extends AbstractEvent
{
/** @var Document */
private $document;
public function __construct(Document $document)
{
$this->document = $document;
}
public function getDocument(): Document
{
return $this->document;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Event;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Input\MarkdownInputInterface;
/**
* Event dispatched when the document is about to be parsed
*/
final class DocumentPreParsedEvent extends AbstractEvent
{
/** @var Document */
private $document;
/** @var MarkdownInputInterface */
private $markdown;
public function __construct(Document $document, MarkdownInputInterface $markdown)
{
$this->document = $document;
$this->markdown = $markdown;
}
public function getDocument(): Document
{
return $this->document;
}
public function getMarkdown(): MarkdownInputInterface
{
return $this->markdown;
}
public function replaceMarkdown(MarkdownInputInterface $markdownInput): void
{
$this->markdown = $markdownInput;
}
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Exception;
final class InvalidOptionException extends \RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Exception;
final class UnexpectedEncodingException extends \RuntimeException
{
}

View File

@@ -0,0 +1,32 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Attributes\Event\AttributesListener;
use League\CommonMark\Extension\Attributes\Parser\AttributesBlockParser;
use League\CommonMark\Extension\Attributes\Parser\AttributesInlineParser;
use League\CommonMark\Extension\ExtensionInterface;
final class AttributesExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addBlockParser(new AttributesBlockParser());
$environment->addInlineParser(new AttributesInlineParser());
$environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener(), 'processDocument']);
}
}

View File

@@ -0,0 +1,141 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Event;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Element\FencedCode;
use League\CommonMark\Block\Element\ListBlock;
use League\CommonMark\Block\Element\ListItem;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Attributes\Node\Attributes;
use League\CommonMark\Extension\Attributes\Node\AttributesInline;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Node\Node;
final class AttributesListener
{
private const DIRECTION_PREFIX = 'prefix';
private const DIRECTION_SUFFIX = 'suffix';
public function processDocument(DocumentParsedEvent $event): void
{
$walker = $event->getDocument()->walker();
while ($event = $walker->next()) {
$node = $event->getNode();
if (!$node instanceof AttributesInline && ($event->isEntering() || !$node instanceof Attributes)) {
continue;
}
[$target, $direction] = self::findTargetAndDirection($node);
if ($target instanceof AbstractBlock || $target instanceof AbstractInline) {
$parent = $target->parent();
if ($parent instanceof ListItem && $parent->parent() instanceof ListBlock && $parent->parent()->isTight()) {
$target = $parent;
}
if ($direction === self::DIRECTION_SUFFIX) {
$attributes = AttributesHelper::mergeAttributes($target, $node->getAttributes());
} else {
$attributes = AttributesHelper::mergeAttributes($node->getAttributes(), $target);
}
$target->data['attributes'] = $attributes;
}
if ($node instanceof AbstractBlock && $node->endsWithBlankLine() && $node->next() && $node->previous()) {
$previous = $node->previous();
if ($previous instanceof AbstractBlock) {
$previous->setLastLineBlank(true);
}
}
$node->detach();
}
}
/**
* @param Node $node
*
* @return array<Node|string|null>
*/
private static function findTargetAndDirection(Node $node): array
{
$target = null;
$direction = null;
$previous = $next = $node;
while (true) {
$previous = self::getPrevious($previous);
$next = self::getNext($next);
if ($previous === null && $next === null) {
if (!$node->parent() instanceof FencedCode) {
$target = $node->parent();
$direction = self::DIRECTION_SUFFIX;
}
break;
}
if ($node instanceof AttributesInline && ($previous === null || ($previous instanceof AbstractInline && $node->isBlock()))) {
continue;
}
if ($previous !== null && !self::isAttributesNode($previous)) {
$target = $previous;
$direction = self::DIRECTION_SUFFIX;
break;
}
if ($next !== null && !self::isAttributesNode($next)) {
$target = $next;
$direction = self::DIRECTION_PREFIX;
break;
}
}
return [$target, $direction];
}
private static function getPrevious(?Node $node = null): ?Node
{
$previous = $node instanceof Node ? $node->previous() : null;
if ($previous instanceof AbstractBlock && $previous->endsWithBlankLine()) {
$previous = null;
}
return $previous;
}
private static function getNext(?Node $node = null): ?Node
{
$next = $node instanceof Node ? $node->next() : null;
if ($node instanceof AbstractBlock && $node->endsWithBlankLine()) {
$next = null;
}
return $next;
}
private static function isAttributesNode(Node $node): bool
{
return $node instanceof Attributes || $node instanceof AttributesInline;
}
}

View File

@@ -0,0 +1,62 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
final class Attributes extends AbstractBlock
{
/** @var array<string, mixed> */
private $attributes;
/**
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
/**
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}
public function canContain(AbstractBlock $block): bool
{
return false;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
$this->setLastLineBlank($cursor->isBlank());
return false;
}
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return false;
}
}

View File

@@ -0,0 +1,50 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Node;
use League\CommonMark\Inline\Element\AbstractInline;
final class AttributesInline extends AbstractInline
{
/** @var array<string, mixed> */
public $attributes;
/** @var bool */
public $block;
/**
* @param array<string, mixed> $attributes
* @param bool $block
*/
public function __construct(array $attributes, bool $block)
{
$this->attributes = $attributes;
$this->block = $block;
$this->data = ['delim' => true]; // TODO: Re-implement as a delimiter?
}
/**
* @return array<string, mixed>
*/
public function getAttributes(): array
{
return $this->attributes;
}
public function isBlock(): bool
{
return $this->block;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Parser;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Extension\Attributes\Node\Attributes;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
final class AttributesBlockParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
$state = $cursor->saveState();
$attributes = AttributesHelper::parseAttributes($cursor);
if ($attributes === []) {
return false;
}
if ($cursor->getNextNonSpaceCharacter() !== null) {
$cursor->restoreState($state);
return false;
}
$context->addBlock(new Attributes($attributes));
$context->setBlocksParsed(true);
return true;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Parser;
use League\CommonMark\Extension\Attributes\Node\AttributesInline;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Inline\Element\Text;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
final class AttributesInlineParser implements InlineParserInterface
{
/**
* {@inheritdoc}
*/
public function getCharacters(): array
{
return ['{'];
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
$char = (string) $cursor->peek(-1);
$attributes = AttributesHelper::parseAttributes($cursor);
if ($attributes === []) {
return false;
}
if ($char === ' ' && ($previousInline = $inlineContext->getContainer()->lastChild()) instanceof Text) {
$previousInline->setContent(\rtrim($previousInline->getContent(), ' '));
}
if ($char === '') {
$cursor->advanceToNextNonSpaceOrNewline();
}
$node = new AttributesInline($attributes, $char === ' ' || $char === '');
$inlineContext->getContainer()->appendChild($node);
return true;
}
}

View File

@@ -0,0 +1,130 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) 2015 Martin Hasoň <martin.hason@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Util;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Util\RegexHelper;
/**
* @internal
*/
final class AttributesHelper
{
/**
* @param Cursor $cursor
*
* @return array<string, mixed>
*/
public static function parseAttributes(Cursor $cursor): array
{
$state = $cursor->saveState();
$cursor->advanceToNextNonSpaceOrNewline();
if ($cursor->getCharacter() !== '{') {
$cursor->restoreState($state);
return [];
}
$cursor->advanceBy(1);
if ($cursor->getCharacter() === ':') {
$cursor->advanceBy(1);
}
$attributes = [];
$regex = '/^\s*([.#][_a-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')(?<!})\s*/i';
while ($attribute = \trim((string) $cursor->match($regex))) {
if ($attribute[0] === '#') {
$attributes['id'] = \substr($attribute, 1);
continue;
}
if ($attribute[0] === '.') {
$attributes['class'][] = \substr($attribute, 1);
continue;
}
[$name, $value] = \explode('=', $attribute, 2);
$first = $value[0];
$last = \substr($value, -1);
if ((($first === '"' && $last === '"') || ($first === "'" && $last === "'")) && \strlen($value) > 1) {
$value = \substr($value, 1, -1);
}
if (\strtolower(\trim($name)) === 'class') {
foreach (\array_filter(\explode(' ', \trim($value))) as $class) {
$attributes['class'][] = $class;
}
} else {
$attributes[trim($name)] = trim($value);
}
}
if ($cursor->match('/}/') === null) {
$cursor->restoreState($state);
return [];
}
if ($attributes === []) {
$cursor->restoreState($state);
return [];
}
if (isset($attributes['class'])) {
$attributes['class'] = \implode(' ', (array) $attributes['class']);
}
return $attributes;
}
/**
* @param AbstractBlock|AbstractInline|array<string, mixed> $attributes1
* @param AbstractBlock|AbstractInline|array<string, mixed> $attributes2
*
* @return array<string, mixed>
*/
public static function mergeAttributes($attributes1, $attributes2): array
{
$attributes = [];
foreach ([$attributes1, $attributes2] as $arg) {
if ($arg instanceof AbstractBlock || $arg instanceof AbstractInline) {
$arg = $arg->data['attributes'] ?? [];
}
/** @var array<string, mixed> $arg */
$arg = (array) $arg;
if (isset($arg['class'])) {
foreach (\array_filter(\explode(' ', \trim($arg['class']))) as $class) {
$attributes['class'][] = $class;
}
unset($arg['class']);
}
$attributes = \array_merge($attributes, $arg);
}
if (isset($attributes['class'])) {
$attributes['class'] = \implode(' ', $attributes['class']);
}
return $attributes;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ExtensionInterface;
final class AutolinkExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addEventListener(DocumentParsedEvent::class, new EmailAutolinkProcessor());
$environment->addEventListener(DocumentParsedEvent::class, new UrlAutolinkProcessor());
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Element\Text;
final class EmailAutolinkProcessor
{
const REGEX = '/([A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+)/';
/**
* @param DocumentParsedEvent $e
*
* @return void
*/
public function __invoke(DocumentParsedEvent $e)
{
$walker = $e->getDocument()->walker();
while ($event = $walker->next()) {
$node = $event->getNode();
if ($node instanceof Text && !($node->parent() instanceof Link)) {
self::processAutolinks($node);
}
}
}
private static function processAutolinks(Text $node): void
{
$contents = \preg_split(self::REGEX, $node->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE);
if ($contents === false || \count($contents) === 1) {
return;
}
$leftovers = '';
foreach ($contents as $i => $content) {
if ($i % 2 === 0) {
$text = $leftovers . $content;
if ($text !== '') {
$node->insertBefore(new Text($leftovers . $content));
}
$leftovers = '';
continue;
}
// Does the URL end with punctuation that should be stripped?
if (\substr($content, -1) === '.') {
// Add the punctuation later
$content = \substr($content, 0, -1);
$leftovers = '.';
}
// The last character cannot be - or _
if (\in_array(\substr($content, -1), ['-', '_'])) {
$node->insertBefore(new Text($content . $leftovers));
$leftovers = '';
continue;
}
$node->insertBefore(new Link('mailto:' . $content, $content));
}
$node->detach();
}
}

View File

@@ -0,0 +1,96 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Extension\Mention\MentionParser;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
@trigger_error(sprintf('%s is deprecated; use %s instead', InlineMentionParser::class, MentionParser::class), E_USER_DEPRECATED);
/**
* @deprecated Use MentionParser instead
*/
final class InlineMentionParser implements InlineParserInterface
{
/** @var string */
private $linkPattern;
/** @var string */
private $handleRegex;
/**
* @param string $linkPattern
* @param string $handleRegex
*/
public function __construct($linkPattern, $handleRegex = '/^[A-Za-z0-9_]+(?!\w)/')
{
$this->linkPattern = $linkPattern;
$this->handleRegex = $handleRegex;
}
public function getCharacters(): array
{
return ['@'];
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
// The @ symbol must not have any other characters immediately prior
$previousChar = $cursor->peek(-1);
if ($previousChar !== null && $previousChar !== ' ') {
// peek() doesn't modify the cursor, so no need to restore state first
return false;
}
// Save the cursor state in case we need to rewind and bail
$previousState = $cursor->saveState();
// Advance past the @ symbol to keep parsing simpler
$cursor->advance();
// Parse the handle
$handle = $cursor->match($this->handleRegex);
if (empty($handle)) {
// Regex failed to match; this isn't a valid Twitter handle
$cursor->restoreState($previousState);
return false;
}
$url = \sprintf($this->linkPattern, $handle);
$inlineContext->getContainer()->appendChild(new Link($url, '@' . $handle));
return true;
}
/**
* @return InlineMentionParser
*/
public static function createTwitterHandleParser()
{
return new self('https://twitter.com/%s', '/^[A-Za-z0-9_]{1,15}(?!\w)/');
}
/**
* @return InlineMentionParser
*/
public static function createGithubHandleParser()
{
// RegEx adapted from https://github.com/shinnn/github-username-regex/blob/master/index.js
return new self('https://www.github.com/%s', '/^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}(?!\w)/');
}
}

View File

@@ -0,0 +1,153 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Element\Text;
final class UrlAutolinkProcessor
{
// RegEx adapted from https://github.com/symfony/symfony/blob/4.2/src/Symfony/Component/Validator/Constraints/UrlValidator.php
const REGEX = '~
(?<=^|[ \\t\\n\\x0b\\x0c\\x0d*_\\~\\(]) # Can only come at the beginning of a line, after whitespace, or certain delimiting characters
(
# Must start with a supported scheme + auth, or "www"
(?:
(?:%s):// # protocol
(?:([\.\pL\pN-]+:)?([\.\pL\pN-]+)@)? # basic auth
|www\.)
(?:
(?:[\pL\pN\pS\-\.])+(?:\.?(?:[\pL\pN]|xn\-\-[\pL\pN-]+)+\.?) # a domain name
| # or
\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} # an IP address
| # or
\[
(?:(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){6})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:::(?:(?:(?:[0-9a-f]{1,4})):){5})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){4})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,1}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){3})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,2}(?:(?:[0-9a-f]{1,4})))?::(?:(?:(?:[0-9a-f]{1,4})):){2})(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,3}(?:(?:[0-9a-f]{1,4})))?::(?:(?:[0-9a-f]{1,4})):)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,4}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:(?:(?:(?:[0-9a-f]{1,4})):(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9]))\.){3}(?:(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])))))))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,5}(?:(?:[0-9a-f]{1,4})))?::)(?:(?:[0-9a-f]{1,4})))|(?:(?:(?:(?:(?:(?:[0-9a-f]{1,4})):){0,6}(?:(?:[0-9a-f]{1,4})))?::))))
\] # an IPv6 address
)
(?::[0-9]+)? # a port (optional)
(?:/ (?:[\pL\pN\-._\~!$&\'()*+,;=:@]|%%[0-9A-Fa-f]{2})* )* # a path
(?:\? (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a query (optional)
(?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional)
)~ixu';
/** @var string */
private $finalRegex;
/**
* @param array<int, string> $allowedProtocols
*/
public function __construct(array $allowedProtocols = ['http', 'https', 'ftp'])
{
$this->finalRegex = \sprintf(self::REGEX, \implode('|', $allowedProtocols));
}
/**
* @param DocumentParsedEvent $e
*
* @return void
*/
public function __invoke(DocumentParsedEvent $e)
{
$walker = $e->getDocument()->walker();
while ($event = $walker->next()) {
$node = $event->getNode();
if ($node instanceof Text && !($node->parent() instanceof Link)) {
self::processAutolinks($node, $this->finalRegex);
}
}
}
private static function processAutolinks(Text $node, string $regex): void
{
$contents = \preg_split($regex, $node->getContent(), -1, PREG_SPLIT_DELIM_CAPTURE);
if ($contents === false || \count($contents) === 1) {
return;
}
$leftovers = '';
foreach ($contents as $i => $content) {
// Even-indexed elements are things before/after the URLs
if ($i % 2 === 0) {
// Insert any left-over characters here as well
$text = $leftovers . $content;
if ($text !== '') {
$node->insertBefore(new Text($leftovers . $content));
}
$leftovers = '';
continue;
}
$leftovers = '';
// Does the URL end with punctuation that should be stripped?
if (\preg_match('/(.+)([?!.,:*_~]+)$/', $content, $matches)) {
// Add the punctuation later
$content = $matches[1];
$leftovers = $matches[2];
}
// Does the URL end with something that looks like an entity reference?
if (\preg_match('/(.+)(&[A-Za-z0-9]+;)$/', $content, $matches)) {
$content = $matches[1];
$leftovers = $matches[2] . $leftovers;
}
// Does the URL need its closing paren chopped off?
if (\substr($content, -1) === ')' && ($diff = self::diffParens($content)) > 0) {
$content = \substr($content, 0, -$diff);
$leftovers = str_repeat(')', $diff) . $leftovers;
}
self::addLink($node, $content);
}
$node->detach();
}
private static function addLink(Text $node, string $url): void
{
// Auto-prefix 'http://' onto 'www' URLs
if (\substr($url, 0, 4) === 'www.') {
$node->insertBefore(new Link('http://' . $url, $url));
return;
}
$node->insertBefore(new Link($url, $url));
}
/**
* @param string $content
*
* @return int
*/
private static function diffParens(string $content): int
{
// Scan the entire autolink for the total number of parentheses.
// If there is a greater number of closing parentheses than opening ones,
// we dont consider ANY of the last characters as part of the autolink,
// in order to facilitate including an autolink inside a parenthesis.
\preg_match_all('/[()]/', $content, $matches);
$charCount = ['(' => 0, ')' => 0];
foreach ($matches[0] as $char) {
$charCount[$char]++;
}
return $charCount[')'] - $charCount['('];
}
}

View File

@@ -0,0 +1,95 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension;
use League\CommonMark\Block\Element as BlockElement;
use League\CommonMark\Block\Parser as BlockParser;
use League\CommonMark\Block\Renderer as BlockRenderer;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor;
use League\CommonMark\Inline\Element as InlineElement;
use League\CommonMark\Inline\Parser as InlineParser;
use League\CommonMark\Inline\Renderer as InlineRenderer;
use League\CommonMark\Util\ConfigurationInterface;
final class CommonMarkCoreExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment
->addBlockParser(new BlockParser\BlockQuoteParser(), 70)
->addBlockParser(new BlockParser\ATXHeadingParser(), 60)
->addBlockParser(new BlockParser\FencedCodeParser(), 50)
->addBlockParser(new BlockParser\HtmlBlockParser(), 40)
->addBlockParser(new BlockParser\SetExtHeadingParser(), 30)
->addBlockParser(new BlockParser\ThematicBreakParser(), 20)
->addBlockParser(new BlockParser\ListParser(), 10)
->addBlockParser(new BlockParser\IndentedCodeParser(), -100)
->addBlockParser(new BlockParser\LazyParagraphParser(), -200)
->addInlineParser(new InlineParser\NewlineParser(), 200)
->addInlineParser(new InlineParser\BacktickParser(), 150)
->addInlineParser(new InlineParser\EscapableParser(), 80)
->addInlineParser(new InlineParser\EntityParser(), 70)
->addInlineParser(new InlineParser\AutolinkParser(), 50)
->addInlineParser(new InlineParser\HtmlInlineParser(), 40)
->addInlineParser(new InlineParser\CloseBracketParser(), 30)
->addInlineParser(new InlineParser\OpenBracketParser(), 20)
->addInlineParser(new InlineParser\BangParser(), 10)
->addBlockRenderer(BlockElement\BlockQuote::class, new BlockRenderer\BlockQuoteRenderer(), 0)
->addBlockRenderer(BlockElement\Document::class, new BlockRenderer\DocumentRenderer(), 0)
->addBlockRenderer(BlockElement\FencedCode::class, new BlockRenderer\FencedCodeRenderer(), 0)
->addBlockRenderer(BlockElement\Heading::class, new BlockRenderer\HeadingRenderer(), 0)
->addBlockRenderer(BlockElement\HtmlBlock::class, new BlockRenderer\HtmlBlockRenderer(), 0)
->addBlockRenderer(BlockElement\IndentedCode::class, new BlockRenderer\IndentedCodeRenderer(), 0)
->addBlockRenderer(BlockElement\ListBlock::class, new BlockRenderer\ListBlockRenderer(), 0)
->addBlockRenderer(BlockElement\ListItem::class, new BlockRenderer\ListItemRenderer(), 0)
->addBlockRenderer(BlockElement\Paragraph::class, new BlockRenderer\ParagraphRenderer(), 0)
->addBlockRenderer(BlockElement\ThematicBreak::class, new BlockRenderer\ThematicBreakRenderer(), 0)
->addInlineRenderer(InlineElement\Code::class, new InlineRenderer\CodeRenderer(), 0)
->addInlineRenderer(InlineElement\Emphasis::class, new InlineRenderer\EmphasisRenderer(), 0)
->addInlineRenderer(InlineElement\HtmlInline::class, new InlineRenderer\HtmlInlineRenderer(), 0)
->addInlineRenderer(InlineElement\Image::class, new InlineRenderer\ImageRenderer(), 0)
->addInlineRenderer(InlineElement\Link::class, new InlineRenderer\LinkRenderer(), 0)
->addInlineRenderer(InlineElement\Newline::class, new InlineRenderer\NewlineRenderer(), 0)
->addInlineRenderer(InlineElement\Strong::class, new InlineRenderer\StrongRenderer(), 0)
->addInlineRenderer(InlineElement\Text::class, new InlineRenderer\TextRenderer(), 0)
;
$deprecatedUseAsterisk = $environment->getConfig('use_asterisk', ConfigurationInterface::MISSING);
if ($deprecatedUseAsterisk !== ConfigurationInterface::MISSING) {
@\trigger_error('The "use_asterisk" configuration option is deprecated in league/commonmark 1.6 and will be replaced with "commonmark > use_asterisk" in 2.0', \E_USER_DEPRECATED);
} else {
$deprecatedUseAsterisk = true;
}
if ($environment->getConfig('commonmark/use_asterisk', $deprecatedUseAsterisk)) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*'));
}
$deprecatedUseUnderscore = $environment->getConfig('use_underscore', ConfigurationInterface::MISSING);
if ($deprecatedUseUnderscore !== ConfigurationInterface::MISSING) {
@\trigger_error('The "use_underscore" configuration option is deprecated in league/commonmark 1.6 and will be replaced with "commonmark > use_underscore" in 2.0', \E_USER_DEPRECATED);
} else {
$deprecatedUseUnderscore = true;
}
if ($environment->getConfig('commonmark/use_underscore', $deprecatedUseUnderscore)) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_'));
}
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DisallowedRawHtml;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class DisallowedRawHtmlBlockRenderer implements BlockRendererInterface, ConfigurationAwareInterface
{
/** @var BlockRendererInterface */
private $htmlBlockRenderer;
public function __construct(BlockRendererInterface $htmlBlockRenderer)
{
$this->htmlBlockRenderer = $htmlBlockRenderer;
}
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
$rendered = $this->htmlBlockRenderer->render($block, $htmlRenderer, $inTightList);
if ($rendered === '') {
return '';
}
// Match these types of tags: <title> </title> <title x="sdf"> <title/> <title />
return preg_replace('/<(\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)[ \/>])/i', '&lt;$1', $rendered);
}
public function setConfiguration(ConfigurationInterface $configuration)
{
if ($this->htmlBlockRenderer instanceof ConfigurationAwareInterface) {
$this->htmlBlockRenderer->setConfiguration($configuration);
}
}
}

View File

@@ -0,0 +1,28 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DisallowedRawHtml;
use League\CommonMark\Block\Element\HtmlBlock;
use League\CommonMark\Block\Renderer\HtmlBlockRenderer;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Inline\Element\HtmlInline;
use League\CommonMark\Inline\Renderer\HtmlInlineRenderer;
final class DisallowedRawHtmlExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addBlockRenderer(HtmlBlock::class, new DisallowedRawHtmlBlockRenderer(new HtmlBlockRenderer()), 50);
$environment->addInlineRenderer(HtmlInline::class, new DisallowedRawHtmlInlineRenderer(new HtmlInlineRenderer()), 50);
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\DisallowedRawHtml;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class DisallowedRawHtmlInlineRenderer implements InlineRendererInterface, ConfigurationAwareInterface
{
/** @var InlineRendererInterface */
private $htmlInlineRenderer;
public function __construct(InlineRendererInterface $htmlBlockRenderer)
{
$this->htmlInlineRenderer = $htmlBlockRenderer;
}
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
{
$rendered = $this->htmlInlineRenderer->render($inline, $htmlRenderer);
if ($rendered === '') {
return '';
}
// Match these types of tags: <title> </title> <title x="sdf"> <title/> <title />
return preg_replace('/<(\/?(?:title|textarea|style|xmp|iframe|noembed|noframes|script|plaintext)[ \/>])/i', '&lt;$1', $rendered);
}
public function setConfiguration(ConfigurationInterface $configuration)
{
if ($this->htmlInlineRenderer instanceof ConfigurationAwareInterface) {
$this->htmlInlineRenderer->setConfiguration($configuration);
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* Original code based on the CommonMark JS reference parser (https://bitly.com/commonmark-js)
* - (c) John MacFarlane
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension;
use League\CommonMark\ConfigurableEnvironmentInterface;
interface ExtensionInterface
{
/**
* @param ConfigurableEnvironmentInterface $environment
*
* @return void
*/
public function register(ConfigurableEnvironmentInterface $environment);
}

View File

@@ -0,0 +1,24 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\ExternalLink;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ExtensionInterface;
final class ExternalLinkExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addEventListener(DocumentParsedEvent::class, new ExternalLinkProcessor($environment), -50);
}
}

View File

@@ -0,0 +1,131 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace League\CommonMark\Extension\ExternalLink;
use League\CommonMark\EnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Inline\Element\Link;
final class ExternalLinkProcessor
{
public const APPLY_NONE = '';
public const APPLY_ALL = 'all';
public const APPLY_EXTERNAL = 'external';
public const APPLY_INTERNAL = 'internal';
/** @var EnvironmentInterface */
private $environment;
public function __construct(EnvironmentInterface $environment)
{
$this->environment = $environment;
}
/**
* @param DocumentParsedEvent $e
*
* @return void
*/
public function __invoke(DocumentParsedEvent $e)
{
$internalHosts = $this->environment->getConfig('external_link/internal_hosts', []);
$openInNewWindow = $this->environment->getConfig('external_link/open_in_new_window', false);
$classes = $this->environment->getConfig('external_link/html_class', '');
$walker = $e->getDocument()->walker();
while ($event = $walker->next()) {
if ($event->isEntering() && $event->getNode() instanceof Link) {
/** @var Link $link */
$link = $event->getNode();
$host = parse_url($link->getUrl(), PHP_URL_HOST);
if (empty($host)) {
// Something is terribly wrong with this URL
continue;
}
if (self::hostMatches($host, $internalHosts)) {
$link->data['external'] = false;
$this->applyRelAttribute($link, false);
continue;
}
// Host does not match our list
$this->markLinkAsExternal($link, $openInNewWindow, $classes);
}
}
}
private function markLinkAsExternal(Link $link, bool $openInNewWindow, string $classes): void
{
$link->data['external'] = true;
$link->data['attributes'] = $link->getData('attributes', []);
$this->applyRelAttribute($link, true);
if ($openInNewWindow) {
$link->data['attributes']['target'] = '_blank';
}
if (!empty($classes)) {
$link->data['attributes']['class'] = trim(($link->data['attributes']['class'] ?? '') . ' ' . $classes);
}
}
private function applyRelAttribute(Link $link, bool $isExternal): void
{
$rel = [];
$options = [
'nofollow' => $this->environment->getConfig('external_link/nofollow', self::APPLY_NONE),
'noopener' => $this->environment->getConfig('external_link/noopener', self::APPLY_EXTERNAL),
'noreferrer' => $this->environment->getConfig('external_link/noreferrer', self::APPLY_EXTERNAL),
];
foreach ($options as $type => $option) {
switch (true) {
case $option === self::APPLY_ALL:
case $isExternal && $option === self::APPLY_EXTERNAL:
case !$isExternal && $option === self::APPLY_INTERNAL:
$rel[] = $type;
}
}
if ($rel === []) {
return;
}
$link->data['attributes']['rel'] = \implode(' ', $rel);
}
/**
* @param string $host
* @param mixed $compareTo
*
* @return bool
*
* @internal This method is only public so we can easily test it. DO NOT USE THIS OUTSIDE OF THIS EXTENSION!
*/
public static function hostMatches(string $host, $compareTo)
{
foreach ((array) $compareTo as $c) {
if (strpos($c, '/') === 0) {
if (preg_match($c, $host)) {
return true;
}
} elseif ($c === $host) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,61 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Block\Element\Paragraph;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Inline\Element\Text;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class AnonymousFootnotesListener implements ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
while ($event = $walker->next()) {
$node = $event->getNode();
if ($node instanceof FootnoteRef && $event->isEntering() && null !== $text = $node->getContent()) {
// Anonymous footnote needs to create a footnote from its content
$existingReference = $node->getReference();
$reference = new Reference(
$existingReference->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix', 'fnref:') . $existingReference->getLabel(),
$existingReference->getTitle()
);
$footnote = new Footnote($reference);
$footnote->addBackref(new FootnoteBackref($reference));
$paragraph = new Paragraph();
$paragraph->appendChild(new Text($text));
$footnote->appendChild($paragraph);
$document->appendChild($footnote);
}
}
}
public function setConfiguration(ConfigurationInterface $config): void
{
$this->config = $config;
}
}

View File

@@ -0,0 +1,100 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class GatherFootnotesListener implements ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
$footnotes = [];
while ($event = $walker->next()) {
if (!$event->isEntering()) {
continue;
}
$node = $event->getNode();
if (!$node instanceof Footnote) {
continue;
}
// Look for existing reference with footnote label
$ref = $document->getReferenceMap()->getReference($node->getReference()->getLabel());
if ($ref !== null) {
// Use numeric title to get footnotes order
$footnotes[\intval($ref->getTitle())] = $node;
} else {
// Footnote call is missing, append footnote at the end
$footnotes[INF] = $node;
}
/*
* Look for all footnote refs pointing to this footnote
* and create each footnote backrefs.
*/
$backrefs = $document->getData(
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $node->getReference()->getDestination(),
[]
);
/** @var Reference $backref */
foreach ($backrefs as $backref) {
$node->addBackref(new FootnoteBackref(new Reference(
$backref->getLabel(),
'#' . $this->config->get('footnote/ref_id_prefix', 'fnref:') . $backref->getLabel(),
$backref->getTitle()
)));
}
}
// Only add a footnote container if there are any
if (\count($footnotes) === 0) {
return;
}
$container = $this->getFootnotesContainer($document);
\ksort($footnotes);
foreach ($footnotes as $footnote) {
$container->appendChild($footnote);
}
}
private function getFootnotesContainer(Document $document): FootnoteContainer
{
$footnoteContainer = new FootnoteContainer();
$document->appendChild($footnoteContainer);
return $footnoteContainer;
}
public function setConfiguration(ConfigurationInterface $config): void
{
$this->config = $config;
}
}

View File

@@ -0,0 +1,86 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Event;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Reference\Reference;
final class NumberFootnotesListener
{
public function onDocumentParsed(DocumentParsedEvent $event): void
{
$document = $event->getDocument();
$walker = $document->walker();
$nextCounter = 1;
$usedLabels = [];
$usedCounters = [];
while ($event = $walker->next()) {
if (!$event->isEntering()) {
continue;
}
$node = $event->getNode();
if (!$node instanceof FootnoteRef) {
continue;
}
$existingReference = $node->getReference();
$label = $existingReference->getLabel();
$counter = $nextCounter;
$canIncrementCounter = true;
if (\array_key_exists($label, $usedLabels)) {
/*
* Reference is used again, we need to point
* to the same footnote. But with a different ID
*/
$counter = $usedCounters[$label];
$label = $label . '__' . ++$usedLabels[$label];
$canIncrementCounter = false;
}
// rewrite reference title to use a numeric link
$newReference = new Reference(
$label,
$existingReference->getDestination(),
(string) $counter
);
// Override reference with numeric link
$node->setReference($newReference);
$document->getReferenceMap()->addReference($newReference);
/*
* Store created references in document for
* creating FootnoteBackrefs
*/
if (false === $document->getData($existingReference->getDestination(), false)) {
$document->data[$existingReference->getDestination()] = [];
}
$document->data[$existingReference->getDestination()][] = $newReference;
$usedLabels[$label] = 1;
$usedCounters[$label] = $nextCounter;
if ($canIncrementCounter) {
$nextCounter++;
}
}
}
}

View File

@@ -0,0 +1,53 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\Footnote\Event\AnonymousFootnotesListener;
use League\CommonMark\Extension\Footnote\Event\GatherFootnotesListener;
use League\CommonMark\Extension\Footnote\Event\NumberFootnotesListener;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Extension\Footnote\Parser\AnonymousFootnoteRefParser;
use League\CommonMark\Extension\Footnote\Parser\FootnoteParser;
use League\CommonMark\Extension\Footnote\Parser\FootnoteRefParser;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteBackrefRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteContainerRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteRefRenderer;
use League\CommonMark\Extension\Footnote\Renderer\FootnoteRenderer;
final class FootnoteExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
{
$environment->addBlockParser(new FootnoteParser(), 51);
$environment->addInlineParser(new AnonymousFootnoteRefParser(), 35);
$environment->addInlineParser(new FootnoteRefParser(), 51);
$environment->addBlockRenderer(FootnoteContainer::class, new FootnoteContainerRenderer());
$environment->addBlockRenderer(Footnote::class, new FootnoteRenderer());
$environment->addInlineRenderer(FootnoteRef::class, new FootnoteRefRenderer());
$environment->addInlineRenderer(FootnoteBackref::class, new FootnoteBackrefRenderer());
$environment->addEventListener(DocumentParsedEvent::class, [new AnonymousFootnotesListener(), 'onDocumentParsed']);
$environment->addEventListener(DocumentParsedEvent::class, [new NumberFootnotesListener(), 'onDocumentParsed']);
$environment->addEventListener(DocumentParsedEvent::class, [new GatherFootnotesListener(), 'onDocumentParsed']);
}
}

View File

@@ -0,0 +1,75 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
use League\CommonMark\Reference\ReferenceInterface;
/**
* @method children() AbstractBlock[]
*/
final class Footnote extends AbstractBlock
{
/**
* @var FootnoteBackref[]
*/
private $backrefs = [];
/**
* @var ReferenceInterface
*/
private $reference;
public function __construct(ReferenceInterface $reference)
{
$this->reference = $reference;
}
public function canContain(AbstractBlock $block): bool
{
return true;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
public function addBackref(FootnoteBackref $backref): self
{
$this->backrefs[] = $backref;
return $this;
}
/**
* @return FootnoteBackref[]
*/
public function getBackrefs(): array
{
return $this->backrefs;
}
}

View File

@@ -0,0 +1,37 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Reference\ReferenceInterface;
/**
* Link from the footnote on the bottom of the document back to the reference
*/
final class FootnoteBackref extends AbstractInline
{
/** @var ReferenceInterface */
private $reference;
public function __construct(ReferenceInterface $reference)
{
$this->reference = $reference;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
}

View File

@@ -0,0 +1,39 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
/**
* @method children() AbstractBlock[]
*/
final class FootnoteContainer extends AbstractBlock
{
public function canContain(AbstractBlock $block): bool
{
return $block instanceof Footnote;
}
public function isCode(): bool
{
return false;
}
public function matchesNextLine(Cursor $cursor): bool
{
return false;
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Node;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Reference\ReferenceInterface;
final class FootnoteRef extends AbstractInline
{
/** @var ReferenceInterface */
private $reference;
/** @var string|null */
private $content;
/**
* @param ReferenceInterface $reference
* @param string|null $content
* @param array<mixed> $data
*/
public function __construct(ReferenceInterface $reference, ?string $content = null, array $data = [])
{
$this->reference = $reference;
$this->content = $content;
$this->data = $data;
}
public function getReference(): ReferenceInterface
{
return $this->reference;
}
public function setReference(ReferenceInterface $reference): FootnoteRef
{
$this->reference = $reference;
return $this;
}
public function getContent(): ?string
{
return $this->content;
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Parser;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
use League\CommonMark\Normalizer\SlugNormalizer;
use League\CommonMark\Normalizer\TextNormalizerInterface;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class AnonymousFootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
/** @var TextNormalizerInterface */
private $slugNormalizer;
public function __construct()
{
$this->slugNormalizer = new SlugNormalizer();
}
public function getCharacters(): array
{
return ['^'];
}
public function parse(InlineParserContext $inlineContext): bool
{
$container = $inlineContext->getContainer();
$cursor = $inlineContext->getCursor();
$nextChar = $cursor->peek();
if ($nextChar !== '[') {
return false;
}
$state = $cursor->saveState();
$m = $cursor->match('/\^\[[^\n^\]]+\]/');
if ($m !== null) {
if (\preg_match('#\^\[([^\]]+)\]#', $m, $matches) > 0) {
$reference = $this->createReference($matches[1]);
$container->appendChild(new FootnoteRef($reference, $matches[1]));
return true;
}
}
$cursor->restoreState($state);
return false;
}
private function createReference(string $label): Reference
{
$refLabel = $this->slugNormalizer->normalize($label);
$refLabel = \mb_substr($refLabel, 0, 20);
return new Reference(
$refLabel,
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $refLabel,
$label
);
}
public function setConfiguration(ConfigurationInterface $config): void
{
$this->config = $config;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Parser;
use League\CommonMark\Block\Parser\BlockParserInterface;
use League\CommonMark\ContextInterface;
use League\CommonMark\Cursor;
use League\CommonMark\Extension\Footnote\Node\Footnote;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\RegexHelper;
final class FootnoteParser implements BlockParserInterface
{
public function parse(ContextInterface $context, Cursor $cursor): bool
{
if ($cursor->isIndented()) {
return false;
}
$match = RegexHelper::matchFirst(
'/^\[\^([^\n^\]]+)\]\:\s/',
$cursor->getLine(),
$cursor->getNextNonSpacePosition()
);
if (!$match) {
return false;
}
$cursor->advanceToNextNonSpaceOrTab();
$cursor->advanceBy(\strlen($match[0]));
$str = $cursor->getRemainder();
\preg_replace('/^\[\^([^\n^\]]+)\]\:\s/', '', $str);
if (\preg_match('/^\[\^([^\n^\]]+)\]\:\s/', $match[0], $matches) > 0) {
$context->addBlock($this->createFootnote($matches[1]));
$context->setBlocksParsed(true);
return true;
}
return false;
}
private function createFootnote(string $label): Footnote
{
return new Footnote(
new Reference($label, $label, $label)
);
}
}

View File

@@ -0,0 +1,72 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Parser;
use League\CommonMark\Extension\Footnote\Node\FootnoteRef;
use League\CommonMark\Inline\Parser\InlineParserInterface;
use League\CommonMark\InlineParserContext;
use League\CommonMark\Reference\Reference;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class FootnoteRefParser implements InlineParserInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public function getCharacters(): array
{
return ['['];
}
public function parse(InlineParserContext $inlineContext): bool
{
$container = $inlineContext->getContainer();
$cursor = $inlineContext->getCursor();
$nextChar = $cursor->peek();
if ($nextChar !== '^') {
return false;
}
$state = $cursor->saveState();
$m = $cursor->match('#\[\^([^\]]+)\]#');
if ($m !== null) {
if (\preg_match('#\[\^([^\]]+)\]#', $m, $matches) > 0) {
$container->appendChild(new FootnoteRef($this->createReference($matches[1])));
return true;
}
}
$cursor->restoreState($state);
return false;
}
private function createReference(string $label): Reference
{
return new Reference(
$label,
'#' . $this->config->get('footnote/footnote_id_prefix', 'fn:') . $label,
$label
);
}
public function setConfiguration(ConfigurationInterface $config): void
{
$this->config = $config;
}
}

View File

@@ -0,0 +1,49 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteBackref;
use League\CommonMark\HtmlElement;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Inline\Renderer\InlineRendererInterface;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class FootnoteBackrefRenderer implements InlineRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public function render(AbstractInline $inline, ElementRendererInterface $htmlRenderer)
{
if (!($inline instanceof FootnoteBackref)) {
throw new \InvalidArgumentException('Incompatible inline type: ' . \get_class($inline));
}
$attrs = $inline->getData('attributes', []);
$attrs['class'] = $attrs['class'] ?? $this->config->get('footnote/backref_class', 'footnote-backref');
$attrs['rev'] = 'footnote';
$attrs['href'] = \mb_strtolower($inline->getReference()->getDestination());
$attrs['role'] = 'doc-backlink';
return '&nbsp;' . new HtmlElement('a', $attrs, '&#8617;', true);
}
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
}

View File

@@ -0,0 +1,52 @@
<?php
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
* (c) Rezo Zero / Ambroise Maupate
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\CommonMark\Extension\Footnote\Renderer;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Block\Renderer\BlockRendererInterface;
use League\CommonMark\ElementRendererInterface;
use League\CommonMark\Extension\Footnote\Node\FootnoteContainer;
use League\CommonMark\HtmlElement;
use League\CommonMark\Util\ConfigurationAwareInterface;
use League\CommonMark\Util\ConfigurationInterface;
final class FootnoteContainerRenderer implements BlockRendererInterface, ConfigurationAwareInterface
{
/** @var ConfigurationInterface */
private $config;
public function render(AbstractBlock $block, ElementRendererInterface $htmlRenderer, bool $inTightList = false)
{
if (!($block instanceof FootnoteContainer)) {
throw new \InvalidArgumentException('Incompatible block type: ' . \get_class($block));
}
$attrs = $block->getData('attributes', []);
$attrs['class'] = $attrs['class'] ?? $this->config->get('footnote/container_class', 'footnotes');
$attrs['role'] = 'doc-endnotes';
$contents = new HtmlElement('ol', [], $htmlRenderer->renderBlocks($block->children()));
if ($this->config->get('footnote/container_add_hr', true)) {
$contents = [new HtmlElement('hr', [], null, true), $contents];
}
return new HtmlElement('div', $attrs, $contents);
}
public function setConfiguration(ConfigurationInterface $configuration)
{
$this->config = $configuration;
}
}

Some files were not shown because too many files have changed in this diff Show More