package and depencies

This commit is contained in:
RafficMohammed
2023-01-08 02:57:24 +05:30
parent d5332eb421
commit 1d54b8bc7f
4309 changed files with 193331 additions and 172289 deletions

View File

@@ -11,24 +11,88 @@
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\Util\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);
expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\Heading::__construct(), 0, 1, 2, 3, 4, 5, 6);
expectedReturnValues(\League\CommonMark\Extension\CommonMark\Node\Block\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'));
registerArgumentsSet('league_commonmark_htmlblock_types', \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_1_CODE_CONTAINER, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_2_COMMENT, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_3, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_4, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_5_CDATA, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_6_BLOCK_ELEMENT, \League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::TYPE_7_MISC_ELEMENT);
expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::__construct(), 0, argumentsSet('league_commonmark_htmlblock_types'));
expectedArguments(\League\CommonMark\Extension\CommonMark\Node\Block\HtmlBlock::setType(), 0, argumentsSet('league_commonmark_htmlblock_types'));
expectedReturnValues(\League\CommonMark\Extension\CommonMark\Node\Block\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_newline_types', \League\CommonMark\Node\Inline\Newline::HARDBREAK, \League\CommonMark\Node\Inline\Newline::SOFTBREAK);
expectedArguments(\League\CommonMark\Node\Inline\Newline::__construct(), 0, argumentsSet('league_commonmark_newline_types'));
expectedReturnValues(\League\CommonMark\Node\Inline\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'));
registerArgumentsSet('league_commonmark_options',
'html_input',
'allow_unsafe_links',
'max_nesting_level',
'renderer',
'renderer/block_separator',
'renderer/inner_separator',
'renderer/soft_break',
'commonmark',
'commonmark/enable_em',
'commonmark/enable_strong',
'commonmark/use_asterisk',
'commonmark/use_underscore',
'commonmark/unordered_list_markers',
'disallowed_raw_html',
'disallowed_raw_html/disallowed_tags',
'external_link',
'external_link/html_class',
'external_link/internal_hosts',
'external_link/nofollow',
'external_link/noopener',
'external_link/noreferrer',
'external_link/open_in_new_window',
'footnote',
'footnote/backref_class',
'footnote/backref_symbol',
'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/fragment_prefix',
'heading_permalink/id_prefix',
'heading_permalink/inner_contents',
'heading_permalink/insert',
'heading_permalink/max_heading_level',
'heading_permalink/min_heading_level',
'heading_permalink/symbol',
'heading_permalink/title',
'mentions',
'smartpunct/double_quote_closer',
'smartpunct/double_quote_opener',
'smartpunct/single_quote_closer',
'smartpunct/single_quote_opener',
'slug_normalizer',
'slug_normalizer/instance',
'slug_normalizer/max_length',
'slug_normalizer/unique',
'table',
'table/wrap',
'table/wrap/attributes',
'table/wrap/enabled',
'table/wrap/tag',
'table_of_contents',
'table_of_contents/html_class',
'table_of_contents/max_heading_level',
'table_of_contents/min_heading_level',
'table_of_contents/normalize',
'table_of_contents/placeholder',
'table_of_contents/position',
'table_of_contents/style',
);
expectedArguments(\League\Config\ConfigurationInterface::get(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\Config\ConfigurationInterface::exists(), 0, argumentsSet('league_commonmark_options'));
expectedArguments(\League\Config\MutableConfigurationInterface::set(), 0, argumentsSet('league_commonmark_options'));
}

View File

@@ -1,842 +0,0 @@
# 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

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
BSD 3-Clause License
Copyright (c) 2014-2019, Colin O'Dell. All rights reserved. Based on commonmark.js,copyright (c) 2014-2018, John MacFarlane
Copyright (c) 2014-2022, Colin O'Dell. All rights reserved. Some code based on commonmark.js (copyright 2014-2018, John MacFarlane) and commonmark-java (copyright 2015-2016, Atlassian Pty Ltd)
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

View File

@@ -3,20 +3,20 @@
[![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)
[![Build Status](https://img.shields.io/github/workflow/status/thephpleague/commonmark/Tests/main.svg?style=flat-square)](https://github.com/thephpleague/commonmark/actions?query=workflow%3ATests+branch%3Amain)
[![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)
[![Psalm Type Coverage](https://shepherd.dev/github/thephpleague/commonmark/coverage.svg)](https://shepherd.dev/github/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]\).
**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:
This project requires PHP 7.4 or higher with the `mbstring` extension. To install it via [Composer] simply run:
``` bash
$ composer require league/commonmark
@@ -32,12 +32,12 @@ $converter = new CommonMarkConverter([
'allow_unsafe_links' => false,
]);
echo $converter->convertToHtml('# Hello World!');
echo $converter->convert('# Hello World!');
// <h1>Hello World!</h1>
```
Or if you want Github-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead:
Or if you want GitHub-Flavored Markdown, use the `GithubFlavoredMarkdownConverter` class instead:
```php
use League\CommonMark\GithubFlavoredMarkdownConverter;
@@ -47,14 +47,14 @@ $converter = new GithubFlavoredMarkdownConverter([
'allow_unsafe_links' => false,
]);
echo $converter->convertToHtml('# Hello World!');
echo $converter->convert('# 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.
🔒 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, consider using a library (like [HTML Purifier](https://github.com/ezyang/htmlpurifier)) to provide additional HTML filtering.
## 📓 Documentation
@@ -64,7 +64,7 @@ Full documentation on advanced usage, configuration, and customization can be fo
Information on how to upgrade to newer versions of this library can be found at <https://commonmark.thephpleague.com/releases>.
## 💻 Github-Flavored Markdown
## 💻 GitHub-Flavored Markdown
The `GithubFlavoredMarkdownConverter` shown earlier is a drop-in replacement for the `CommonMarkConverter` which adds additional features found in the GFM spec:
@@ -82,12 +82,13 @@ See the [Extensions documentation](https://commonmark.thephpleague.com/customiza
- [CakePHP 3](https://github.com/gourmet/common-mark)
- [Drupal](https://www.drupal.org/project/markdown)
- [Laravel 4 & 5](https://github.com/GrahamCampbell/Laravel-Markdown)
- [Laravel 4+](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)
- [Laravel CommonMark Blog](https://github.com/spekulatius/laravel-commonmark-blog)
### Included Extensions
@@ -100,6 +101,8 @@ Custom parsers/renderers can be bundled into extensions which extend CommonMark.
- [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.
- [Lazy Image extension](https://github.com/simonvomeyser/commonmark-ext-lazy-image) - Adds various options for lazy loading of images.
- [Marker Extension](https://github.com/noah1400/commonmark-marker-extension) - Adds support of highlighted text (`<mark>` HTML tag)
Others can be found on [Packagist under the `commonmark-extension` package type](https://packagist.org/packages/league/commonmark?type=commonmark-extension).
@@ -117,9 +120,9 @@ Any classes or methods marked `@internal` are not intended for use outside of th
## 🛠️ 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 **minor** version (e.g. `2.0` -> `2.1`) is released, the previous one (`2.0`) 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.
When a new **major** version is released (e.g. `1.6` -> `2.0`), the previous one (`1.6`) 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.)
@@ -140,7 +143,7 @@ Contributions to this library are **welcome**, especially ones that:
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.
Please see [CONTRIBUTING](https://github.com/thephpleague/commonmark/blob/main/.github/CONTRIBUTING.md) for additional details.
## 🧪 Testing
@@ -171,8 +174,9 @@ This code is partially based on the [CommonMark JS reference implementation][com
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
- [Blackfire](https://www.blackfire.io/) for providing an Open-Source Profiler subscription
- [JetBrains](https://www.jetbrains.com/) for supporting this project with complimentary [PhpStorm](https://www.jetbrains.com/phpstorm/) licenses
- [Taylor Otwell](https://twitter.com/taylorotwell) for sponsoring this project through GitHub sponsors
Are you interested in sponsoring development of this project? See <https://www.colinodell.com/sponsor> for a list of ways to contribute.
@@ -203,7 +207,7 @@ This project is used by [Drupal](https://www.drupal.org/project/markdown), [Lara
[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/
[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

View File

@@ -1,181 +0,0 @@
#!/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;
}
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "league/commonmark",
"type": "library",
"description": "Highly-extensible PHP Markdown parser which fully supports the CommonMark spec and Github-Flavored Markdown (GFM)",
"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",
@@ -15,40 +15,61 @@
],
"support": {
"docs": "https://commonmark.thephpleague.com/",
"forum": "https://github.com/thephpleague/commonmark/discussions",
"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": "*"
"php": "^7.4 || ^8.0",
"ext-mbstring": "*",
"league/config": "^1.1.1",
"psr/event-dispatcher": "^1.0",
"symfony/deprecation-contracts": "^2.1 || ^3.0",
"symfony/polyfill-php80": "^1.16"
},
"require-dev": {
"ext-json": "*",
"cebe/markdown": "~1.0",
"commonmark/commonmark.js": "0.29.2",
"erusev/parsedown": "~1.0",
"cebe/markdown": "^1.0",
"commonmark/cmark": "0.30.0",
"commonmark/commonmark.js": "0.30.0",
"composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4",
"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"
"michelf/php-markdown": "^1.4 || ^2.0",
"nyholm/psr7": "^1.5",
"phpstan/phpstan": "^1.8.2",
"phpunit/phpunit": "^9.5.21",
"scrutinizer/ocular": "^1.8.1",
"symfony/finder": "^5.3 | ^6.0",
"symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0",
"unleashedtech/php-coding-standard": "^3.1.1",
"vimeo/psalm": "^4.24.0 || ^5.0.0"
},
"minimum-stability": "dev",
"prefer-stable": true,
"conflict": {
"scrutinizer/ocular": "1.7.*"
"minimum-stability": "beta",
"suggest": {
"symfony/yaml": "v2.3+ required if using the Front Matter extension"
},
"repositories": [
{
"type": "package",
"package": {
"name": "commonmark/commonmark.js",
"version": "0.29.2",
"version": "0.30.0",
"dist": {
"url": "https://github.com/commonmark/commonmark.js/archive/0.29.2.zip",
"url": "https://github.com/commonmark/commonmark.js/archive/0.30.0.zip",
"type": "zip"
}
}
},
{
"type": "package",
"package": {
"name": "commonmark/cmark",
"version": "0.30.0",
"dist": {
"url": "https://github.com/commonmark/cmark/archive/0.30.0.zip",
"type": "zip"
}
}
@@ -73,19 +94,32 @@
"autoload-dev": {
"psr-4": {
"League\\CommonMark\\Tests\\Unit\\": "tests/unit",
"League\\CommonMark\\Tests\\Functional\\": "tests/functional"
"League\\CommonMark\\Tests\\Functional\\": "tests/functional",
"League\\CommonMark\\Tests\\PHPStan\\": "tests/phpstan"
}
},
"bin": ["bin/commonmark"],
"scripts": {
"phpcs": "phpcs",
"phpstan": "phpstan analyse",
"phpunit": "phpunit --no-coverage",
"psalm": "psalm --stats",
"test": [
"@phpcs",
"@phpstan",
"@psalm",
"@phpunit"
]
},
"extra": {
"branch-alias": {
"dev-main": "2.4-dev"
}
},
"config": {
"allow-plugins": {
"composer/package-versions-deprecated": true,
"dealerdirect/phpcodesniffer-composer-installer": true
},
"sort-packages": true
}
}

View File

@@ -1,222 +0,0 @@
<?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

@@ -1,55 +0,0 @@
<?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

@@ -1,51 +0,0 @@
<?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

@@ -1,201 +0,0 @@
<?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

@@ -1,80 +0,0 @@
<?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

@@ -1,104 +0,0 @@
<?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

@@ -1,72 +0,0 @@
<?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

@@ -1,123 +0,0 @@
<?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

@@ -1,73 +0,0 @@
<?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

@@ -1,98 +0,0 @@
<?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

@@ -1,44 +0,0 @@
<?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

@@ -1,35 +0,0 @@
<?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

@@ -1,51 +0,0 @@
<?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

@@ -1,29 +0,0 @@
<?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

@@ -1,41 +0,0 @@
<?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

@@ -1,47 +0,0 @@
<?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

@@ -1,59 +0,0 @@
<?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

@@ -1,43 +0,0 @@
<?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

@@ -1,32 +0,0 @@
<?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

@@ -1,154 +0,0 @@
<?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

@@ -1,81 +0,0 @@
<?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

@@ -1,50 +0,0 @@
<?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

@@ -1,31 +0,0 @@
<?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

@@ -1,40 +0,0 @@
<?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

@@ -1,52 +0,0 @@
<?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

@@ -1,43 +0,0 @@
<?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

@@ -1,59 +0,0 @@
<?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

@@ -1,46 +0,0 @@
<?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

@@ -1,56 +0,0 @@
<?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

@@ -1,60 +0,0 @@
<?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

@@ -1,45 +0,0 @@
<?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

@@ -1,41 +0,0 @@
<?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

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -14,39 +16,31 @@
namespace League\CommonMark;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
/**
* Converts CommonMark-compatible Markdown to HTML.
*/
class CommonMarkConverter extends MarkdownConverter
final class CommonMarkConverter extends MarkdownConverter
{
/**
* The currently-installed version.
* Create a new Markdown converter pre-configured for CommonMark
*
* 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.
* @param array<string, mixed> $config
*/
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)
public function __construct(array $config = [])
{
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);
}
$environment = new Environment($config);
$environment->addExtension(new CommonMarkCoreExtension());
parent::__construct($environment);
}
public function getEnvironment(): Environment
{
\assert($this->environment instanceof Environment);
return $this->environment;
}
}

View File

@@ -1,110 +0,0 @@
<?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;
}

View File

@@ -1,201 +0,0 @@
<?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

@@ -1,99 +0,0 @@
<?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

@@ -1,84 +0,0 @@
<?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

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -11,11 +13,15 @@
namespace League\CommonMark;
use League\CommonMark\Output\RenderedContentInterface;
/**
* Interface for a service which converts CommonMark to HTML.
*
* @deprecated ConverterInterface is deprecated since league/commonmark 1.4, use MarkdownConverterInterface instead
* Interface for a service which converts content from one format (like Markdown) to another (like HTML).
*/
interface ConverterInterface extends MarkdownConverterInterface
interface ConverterInterface
{
/**
* @throws \RuntimeException
*/
public function convert(string $input): RenderedContentInterface;
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -14,58 +16,50 @@
namespace League\CommonMark\Delimiter;
use League\CommonMark\Inline\Element\AbstractStringContainer;
use League\CommonMark\Node\Inline\AbstractStringContainer;
final class Delimiter implements DelimiterInterface
{
/** @var string */
private $char;
/** @psalm-readonly */
private string $char;
/** @var int */
private $length;
/** @psalm-readonly-allow-private-mutation */
private int $length;
/** @var int */
private $originalLength;
/** @psalm-readonly */
private int $originalLength;
/** @var AbstractStringContainer */
private $inlineNode;
/** @psalm-readonly */
private AbstractStringContainer $inlineNode;
/** @var DelimiterInterface|null */
private $previous;
/** @psalm-readonly-allow-private-mutation */
private ?DelimiterInterface $previous = null;
/** @var DelimiterInterface|null */
private $next;
/** @psalm-readonly-allow-private-mutation */
private ?DelimiterInterface $next = null;
/** @var bool */
private $canOpen;
/** @psalm-readonly */
private bool $canOpen;
/** @var bool */
private $canClose;
/** @psalm-readonly */
private bool $canClose;
/** @var bool */
private $active;
/** @psalm-readonly-allow-private-mutation */
private bool $active;
/** @var int|null */
private $index;
/** @psalm-readonly */
private ?int $index = null;
/**
* @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->char = $char;
$this->length = $numDelims;
$this->originalLength = $numDelims;
$this->inlineNode = $node;
$this->canOpen = $canOpen;
$this->canClose = $canClose;
$this->active = true;
$this->index = $index;
$this->inlineNode = $node;
$this->canOpen = $canOpen;
$this->canClose = $canClose;
$this->active = true;
$this->index = $index;
}
public function canClose(): bool
@@ -73,16 +67,6 @@ final class Delimiter implements DelimiterInterface
return $this->canClose;
}
/**
* @param bool $canClose
*
* @return void
*/
public function setCanClose(bool $canClose)
{
$this->canClose = $canClose;
}
public function canOpen(): bool
{
return $this->canOpen;
@@ -93,7 +77,7 @@ final class Delimiter implements DelimiterInterface
return $this->active;
}
public function setActive(bool $active)
public function setActive(bool $active): void
{
$this->active = $active;
}
@@ -113,7 +97,7 @@ final class Delimiter implements DelimiterInterface
return $this->next;
}
public function setNext(?DelimiterInterface $next)
public function setNext(?DelimiterInterface $next): void
{
$this->next = $next;
}
@@ -123,7 +107,7 @@ final class Delimiter implements DelimiterInterface
return $this->length;
}
public function setLength(int $length)
public function setLength(int $length): void
{
$this->length = $length;
}
@@ -143,10 +127,8 @@ final class Delimiter implements DelimiterInterface
return $this->previous;
}
public function setPrevious(?DelimiterInterface $previous): DelimiterInterface
public function setPrevious(?DelimiterInterface $previous): void
{
$this->previous = $previous;
return $this;
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -14,7 +16,7 @@
namespace League\CommonMark\Delimiter;
use League\CommonMark\Inline\Element\AbstractStringContainer;
use League\CommonMark\Node\Inline\AbstractStringContainer;
interface DelimiterInterface
{
@@ -24,37 +26,19 @@ interface DelimiterInterface
public function isActive(): bool;
/**
* @param bool $active
*
* @return void
*/
public function setActive(bool $active);
public function setActive(bool $active): void;
/**
* @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 setNext(?DelimiterInterface $next): void;
public function getLength(): int;
/**
* @param int $length
*
* @return void
*/
public function setLength(int $length);
public function setLength(int $length): void;
public function getOriginalLength(): int;
@@ -62,10 +46,5 @@ interface DelimiterInterface
public function getPrevious(): ?DelimiterInterface;
/**
* @param DelimiterInterface|null $previous
*
* @return mixed|void
*/
public function setPrevious(?DelimiterInterface $previous);
public function setPrevious(?DelimiterInterface $previous): void;
}

View File

@@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace League\CommonMark\Delimiter;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Node\Inline\Text;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
use League\CommonMark\Util\RegexHelper;
/**
* Delimiter parsing is implemented as an Inline Parser with the lowest-possible priority
*
* @internal
*/
final class DelimiterParser implements InlineParserInterface
{
private DelimiterProcessorCollection $collection;
public function __construct(DelimiterProcessorCollection $collection)
{
$this->collection = $collection;
}
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::oneOf(...$this->collection->getDelimiterCharacters());
}
public function parse(InlineParserContext $inlineContext): bool
{
$character = $inlineContext->getFullMatch();
$numDelims = 0;
$cursor = $inlineContext->getCursor();
$processor = $this->collection->getDelimiterProcessor($character);
if ($processor === null) {
throw new \LogicException('Delimiter processor should never be null here');
}
$charBefore = $cursor->peek(-1);
if ($charBefore === null) {
$charBefore = "\n";
}
while ($cursor->peek($numDelims) === $character) {
++$numDelims;
}
if ($numDelims < $processor->getMinLength()) {
return false;
}
$cursor->advanceBy($numDelims);
$charAfter = $cursor->getCurrentCharacter();
if ($charAfter === null) {
$charAfter = "\n";
}
[$canOpen, $canClose] = self::determineCanOpenOrClose($charBefore, $charAfter, $character, $processor);
$node = new Text(\str_repeat($character, $numDelims), [
'delim' => true,
]);
$inlineContext->getContainer()->appendChild($node);
// Add entry to stack to this opener
if ($canOpen || $canClose) {
$delimiter = new Delimiter($character, $numDelims, $node, $canOpen, $canClose);
$inlineContext->getDelimiterStack()->push($delimiter);
}
return true;
}
/**
* @return bool[]
*/
private static function determineCanOpenOrClose(string $charBefore, string $charAfter, string $character, DelimiterProcessorInterface $delimiterProcessor): array
{
$afterIsWhitespace = \preg_match(RegexHelper::REGEX_UNICODE_WHITESPACE_CHAR, $charAfter);
$afterIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charAfter);
$beforeIsWhitespace = \preg_match(RegexHelper::REGEX_UNICODE_WHITESPACE_CHAR, $charBefore);
$beforeIsPunctuation = \preg_match(RegexHelper::REGEX_PUNCTUATION, $charBefore);
$leftFlanking = ! $afterIsWhitespace && (! $afterIsPunctuation || $beforeIsWhitespace || $beforeIsPunctuation);
$rightFlanking = ! $beforeIsWhitespace && (! $beforeIsPunctuation || $afterIsWhitespace || $afterIsPunctuation);
if ($character === '_') {
$canOpen = $leftFlanking && (! $rightFlanking || $beforeIsPunctuation);
$canClose = $rightFlanking && (! $leftFlanking || $afterIsPunctuation);
} else {
$canOpen = $leftFlanking && $character === $delimiterProcessor->getOpeningCharacter();
$canClose = $rightFlanking && $character === $delimiterProcessor->getClosingCharacter();
}
return [$canOpen, $canClose];
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -18,21 +20,14 @@
namespace League\CommonMark\Delimiter;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Inline\AdjacentTextMerger;
use League\CommonMark\Node\Inline\AdjacentTextMerger;
final class DelimiterStack
{
/**
* @var DelimiterInterface|null
*/
private $top;
/** @psalm-readonly-allow-private-mutation */
private ?DelimiterInterface $top = null;
/**
* @param DelimiterInterface $newDelimiter
*
* @return void
*/
public function push(DelimiterInterface $newDelimiter)
public function push(DelimiterInterface $newDelimiter): void
{
$newDelimiter->setPrevious($this->top);
@@ -43,7 +38,7 @@ final class DelimiterStack
$this->top = $newDelimiter;
}
private function findEarliest(DelimiterInterface $stackBottom = null): ?DelimiterInterface
private function findEarliest(?DelimiterInterface $stackBottom = null): ?DelimiterInterface
{
$delimiter = $this->top;
while ($delimiter !== null && $delimiter->getPrevious() !== $stackBottom) {
@@ -53,14 +48,10 @@ final class DelimiterStack
return $delimiter;
}
/**
* @param DelimiterInterface $delimiter
*
* @return void
*/
public function removeDelimiter(DelimiterInterface $delimiter)
public function removeDelimiter(DelimiterInterface $delimiter): void
{
if ($delimiter->getPrevious() !== null) {
/** @psalm-suppress PossiblyNullReference */
$delimiter->getPrevious()->setNext($delimiter->getNext());
}
@@ -68,6 +59,7 @@ final class DelimiterStack
// top of stack
$this->top = $delimiter->getPrevious();
} else {
/** @psalm-suppress PossiblyNullReference */
$delimiter->getNext()->setPrevious($delimiter->getPrevious());
}
}
@@ -88,24 +80,14 @@ final class DelimiterStack
}
}
/**
* @param DelimiterInterface|null $stackBottom
*
* @return void
*/
public function removeAll(DelimiterInterface $stackBottom = null)
public function removeAll(?DelimiterInterface $stackBottom = null): void
{
while ($this->top && $this->top !== $stackBottom) {
$this->removeDelimiter($this->top);
}
}
/**
* @param string $character
*
* @return void
*/
public function removeEarlierMatches(string $character)
public function removeEarlierMatches(string $character): void
{
$opener = $this->top;
while ($opener !== null) {
@@ -119,33 +101,26 @@ final class DelimiterStack
/**
* @param string|string[] $characters
*
* @return DelimiterInterface|null
*/
public function searchByCharacter($characters): ?DelimiterInterface
{
if (!\is_array($characters)) {
if (! \is_array($characters)) {
$characters = [$characters];
}
$opener = $this->top;
while ($opener !== null) {
if (\in_array($opener->getChar(), $characters)) {
if (\in_array($opener->getChar(), $characters, true)) {
break;
}
$opener = $opener->getPrevious();
}
return $opener;
}
/**
* @param DelimiterInterface|null $stackBottom
* @param DelimiterProcessorCollection $processors
*
* @return void
*/
public function processDelimiters(?DelimiterInterface $stackBottom, DelimiterProcessorCollection $processors)
public function processDelimiters(?DelimiterInterface $stackBottom, DelimiterProcessorCollection $processors): void
{
$openersBottom = [];
@@ -157,31 +132,32 @@ final class DelimiterStack
$delimiterChar = $closer->getChar();
$delimiterProcessor = $processors->getDelimiterProcessor($delimiterChar);
if (!$closer->canClose() || $delimiterProcessor === null) {
if (! $closer->canClose() || $delimiterProcessor === null) {
$closer = $closer->getNext();
continue;
}
$openingDelimiterChar = $delimiterProcessor->getOpeningCharacter();
$useDelims = 0;
$openerFound = false;
$useDelims = 0;
$openerFound = false;
$potentialOpenerFound = false;
$opener = $closer->getPrevious();
$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);
$useDelims = $delimiterProcessor->getDelimiterUse($opener, $closer);
if ($useDelims > 0) {
$openerFound = true;
break;
}
}
$opener = $opener->getPrevious();
}
if (!$openerFound) {
if (!$potentialOpenerFound) {
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
@@ -190,16 +166,19 @@ final class DelimiterStack
// 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()) {
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;
}
\assert($opener !== null);
$openerNode = $opener->getInlineNode();
$closerNode = $closer->getInlineNode();
@@ -207,8 +186,8 @@ final class DelimiterStack
$opener->setLength($opener->getLength() - $useDelims);
$closer->setLength($closer->getLength() - $useDelims);
$openerNode->setContent(\substr($openerNode->getContent(), 0, -$useDelims));
$closerNode->setContent(\substr($closerNode->getContent(), 0, -$useDelims));
$openerNode->setLiteral(\substr($openerNode->getLiteral(), 0, -$useDelims));
$closerNode->setLiteral(\substr($closerNode->getLiteral(), 0, -$useDelims));
$this->removeDelimitersBetween($opener, $closer);
// The delimiter processor can re-parent the nodes between opener and closer,
@@ -221,6 +200,7 @@ final class DelimiterStack
$this->removeDelimiterAndNode($opener);
}
// phpcs:disable SlevomatCodingStandard.ControlStructures.EarlyExit.EarlyExitNotUsed
if ($closer->getLength() === 0) {
$next = $closer->getNext();
$this->removeDelimiterAndNode($closer);

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -19,10 +21,14 @@ namespace League\CommonMark\Delimiter\Processor;
final class DelimiterProcessorCollection implements DelimiterProcessorCollectionInterface
{
/** @var array<string,DelimiterProcessorInterface>|DelimiterProcessorInterface[] */
private $processorsByChar = [];
/**
* @var array<string,DelimiterProcessorInterface>|DelimiterProcessorInterface[]
*
* @psalm-readonly-allow-private-mutation
*/
private array $processorsByChar = [];
public function add(DelimiterProcessorInterface $processor)
public function add(DelimiterProcessorInterface $processor): void
{
$opening = $processor->getOpeningCharacter();
$closing = $processor->getClosingCharacter();
@@ -45,6 +51,9 @@ final class DelimiterProcessorCollection implements DelimiterProcessorCollection
return $this->processorsByChar[$char] ?? null;
}
/**
* @return string[]
*/
public function getDelimiterCharacters(): array
{
return \array_keys($this->processorsByChar);
@@ -70,4 +79,9 @@ final class DelimiterProcessorCollection implements DelimiterProcessorCollection
$s->add($new);
$this->processorsByChar[$opening] = $s;
}
public function count(): int
{
return \count($this->processorsByChar);
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -17,7 +19,7 @@
namespace League\CommonMark\Delimiter\Processor;
interface DelimiterProcessorCollectionInterface
interface DelimiterProcessorCollectionInterface extends \Countable
{
/**
* Add the given delim processor to the collection
@@ -25,17 +27,11 @@ interface DelimiterProcessorCollectionInterface
* @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);
public function add(DelimiterProcessorInterface $processor): void;
/**
* Returns the delim processor which handles the given character if one exists
*
* @param string $char
*
* @return DelimiterProcessorInterface|null
*/
public function getDelimiterProcessor(string $char): ?DelimiterProcessorInterface;

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -18,7 +20,7 @@
namespace League\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Inline\Element\AbstractStringContainer;
use League\CommonMark\Node\Inline\AbstractStringContainer;
/**
* Interface for a delimiter processor
@@ -29,8 +31,6 @@ 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;
@@ -40,8 +40,6 @@ interface DelimiterProcessorInterface
* 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;
@@ -49,8 +47,6 @@ interface DelimiterProcessorInterface
* Minimum number of delimiter characters that are needed to active this.
*
* Must be at least 1.
*
* @return int
*/
public function getMinLength(): int;
@@ -64,8 +60,6 @@ interface DelimiterProcessorInterface
*
* @param DelimiterInterface $opener The opening delimiter run
* @param DelimiterInterface $closer The closing delimiter run
*
* @return int
*/
public function getDelimiterUse(DelimiterInterface $opener, DelimiterInterface $closer): int;
@@ -79,8 +73,6 @@ interface DelimiterProcessorInterface
* @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);
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void;
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -15,7 +17,7 @@
namespace League\CommonMark\Delimiter\Processor;
use League\CommonMark\Delimiter\DelimiterInterface;
use League\CommonMark\Inline\Element\AbstractStringContainer;
use League\CommonMark\Node\Inline\AbstractStringContainer;
/**
* An implementation of DelimiterProcessorInterface that dispatches all calls to two or more other DelimiterProcessors
@@ -27,14 +29,18 @@ use League\CommonMark\Inline\Element\AbstractStringContainer;
*/
final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
{
/** @var string */
private $delimiterChar;
/** @psalm-readonly */
private string $delimiterChar;
/** @var int */
private $minLength = 0;
/** @psalm-readonly-allow-private-mutation */
private int $minLength = 0;
/** @var array<int, DelimiterProcessorInterface>|DelimiterProcessorInterface[] */
private $processors = []; // keyed by minLength in reverse order
/**
* @var array<int, DelimiterProcessorInterface>|DelimiterProcessorInterface[]
*
* @psalm-readonly-allow-private-mutation
*/
private array $processors = []; // keyed by minLength in reverse order
public function __construct(string $char, DelimiterProcessorInterface $processor)
{
@@ -59,12 +65,8 @@ final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
/**
* Adds the given processor to this staggered delimiter processor
*
* @param DelimiterProcessorInterface $processor
*
* @return void
*/
public function add(DelimiterProcessorInterface $processor)
public function add(DelimiterProcessorInterface $processor): void
{
$len = $processor->getMinLength();
@@ -83,7 +85,7 @@ final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
return $this->findProcessor($opener->getLength())->getDelimiterUse($opener, $closer);
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse)
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
{
$this->findProcessor($delimiterUse)->process($opener, $closer, $delimiterUse);
}
@@ -98,8 +100,8 @@ final class StaggeredDelimiterProcessor implements DelimiterProcessorInterface
}
// Just use the first one in our list
/** @var DelimiterProcessorInterface $first */
$first = \reset($this->processors);
\assert($first instanceof DelimiterProcessorInterface);
return $first;
}

View File

@@ -1,237 +0,0 @@
<?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

@@ -1,64 +0,0 @@
<?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

@@ -1,435 +0,0 @@
<?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,446 @@
<?php
declare(strict_types=1);
/*
* 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\Environment;
use League\CommonMark\Delimiter\DelimiterParser;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Event\ListenerData;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Extension\GithubFlavoredMarkdownExtension;
use League\CommonMark\Normalizer\SlugNormalizer;
use League\CommonMark\Normalizer\TextNormalizerInterface;
use League\CommonMark\Normalizer\UniqueSlugNormalizer;
use League\CommonMark\Normalizer\UniqueSlugNormalizerInterface;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Block\SkipLinesStartingWithLettersParser;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlFilter;
use League\CommonMark\Util\PrioritizedList;
use League\Config\Configuration;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
use Nette\Schema\Expect;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\EventDispatcher\ListenerProviderInterface;
use Psr\EventDispatcher\StoppableEventInterface;
final class Environment implements EnvironmentInterface, EnvironmentBuilderInterface, ListenerProviderInterface
{
/**
* @var ExtensionInterface[]
*
* @psalm-readonly-allow-private-mutation
*/
private array $extensions = [];
/**
* @var ExtensionInterface[]
*
* @psalm-readonly-allow-private-mutation
*/
private array $uninitializedExtensions = [];
/** @psalm-readonly-allow-private-mutation */
private bool $extensionsInitialized = false;
/**
* @var PrioritizedList<BlockStartParserInterface>
*
* @psalm-readonly
*/
private PrioritizedList $blockStartParsers;
/**
* @var PrioritizedList<InlineParserInterface>
*
* @psalm-readonly
*/
private PrioritizedList $inlineParsers;
/** @psalm-readonly */
private DelimiterProcessorCollection $delimiterProcessors;
/**
* @var array<string, PrioritizedList<NodeRendererInterface>>
*
* @psalm-readonly-allow-private-mutation
*/
private array $renderersByClass = [];
/**
* @var PrioritizedList<ListenerData>
*
* @psalm-readonly-allow-private-mutation
*/
private PrioritizedList $listenerData;
private ?EventDispatcherInterface $eventDispatcher = null;
/** @psalm-readonly */
private Configuration $config;
private ?TextNormalizerInterface $slugNormalizer = null;
/**
* @param array<string, mixed> $config
*/
public function __construct(array $config = [])
{
$this->config = self::createDefaultConfiguration();
$this->config->merge($config);
$this->blockStartParsers = new PrioritizedList();
$this->inlineParsers = new PrioritizedList();
$this->listenerData = new PrioritizedList();
$this->delimiterProcessors = new DelimiterProcessorCollection();
// Performance optimization: always include a block "parser" that aborts parsing if a line starts with a letter
// and is therefore unlikely to match any lines as a block start.
$this->addBlockStartParser(new SkipLinesStartingWithLettersParser(), 249);
}
public function getConfiguration(): ConfigurationInterface
{
return $this->config->reader();
}
/**
* @deprecated Environment::mergeConfig() is deprecated since league/commonmark v2.0 and will be removed in v3.0. Configuration should be set when instantiating the environment instead.
*
* @param array<string, mixed> $config
*/
public function mergeConfig(array $config): void
{
@\trigger_error('Environment::mergeConfig() is deprecated since league/commonmark v2.0 and will be removed in v3.0. Configuration should be set when instantiating the environment instead.', \E_USER_DEPRECATED);
$this->assertUninitialized('Failed to modify configuration.');
$this->config->merge($config);
}
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add block start parser.');
$this->blockStartParsers->add($parser, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($parser);
return $this;
}
public function addInlineParser(InlineParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add inline parser.');
$this->inlineParsers->add($parser, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($parser);
return $this;
}
public function addDelimiterProcessor(DelimiterProcessorInterface $processor): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add delimiter processor.');
$this->delimiterProcessors->add($processor);
$this->injectEnvironmentAndConfigurationIfNeeded($processor);
return $this;
}
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add renderer.');
if (! isset($this->renderersByClass[$nodeClass])) {
$this->renderersByClass[$nodeClass] = new PrioritizedList();
}
$this->renderersByClass[$nodeClass]->add($renderer, $priority);
$this->injectEnvironmentAndConfigurationIfNeeded($renderer);
return $this;
}
/**
* {@inheritDoc}
*/
public function getBlockStartParsers(): iterable
{
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->blockStartParsers->getIterator();
}
public function getDelimiterProcessors(): DelimiterProcessorCollection
{
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->delimiterProcessors;
}
/**
* {@inheritDoc}
*/
public function getRenderersForClass(string $nodeClass): iterable
{
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
// If renderers are defined for this specific class, return them immediately
if (isset($this->renderersByClass[$nodeClass])) {
return $this->renderersByClass[$nodeClass];
}
/** @psalm-suppress TypeDoesNotContainType -- Bug: https://github.com/vimeo/psalm/issues/3332 */
while (\class_exists($parent ??= $nodeClass) && $parent = \get_parent_class($parent)) {
if (! isset($this->renderersByClass[$parent])) {
continue;
}
// "Cache" this result to avoid future loops
return $this->renderersByClass[$nodeClass] = $this->renderersByClass[$parent];
}
return [];
}
/**
* {@inheritDoc}
*/
public function getExtensions(): iterable
{
return $this->extensions;
}
/**
* Add a single extension
*
* @return $this
*/
public function addExtension(ExtensionInterface $extension): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add extension.');
$this->extensions[] = $extension;
$this->uninitializedExtensions[] = $extension;
if ($extension instanceof ConfigurableExtensionInterface) {
$extension->configureSchema($this->config);
}
return $this;
}
private function initializeExtensions(): void
{
// Initialize the slug normalizer
$this->getSlugNormalizer();
// Ask all extensions to register their components
while (\count($this->uninitializedExtensions) > 0) {
foreach ($this->uninitializedExtensions as $i => $extension) {
$extension->register($this);
unset($this->uninitializedExtensions[$i]);
}
}
$this->extensionsInitialized = true;
// Create the special delimiter parser if any processors were registered
if ($this->delimiterProcessors->count() > 0) {
$this->inlineParsers->add(new DelimiterParser($this->delimiterProcessors), PHP_INT_MIN);
}
}
private function injectEnvironmentAndConfigurationIfNeeded(object $object): void
{
if ($object instanceof EnvironmentAwareInterface) {
$object->setEnvironment($this);
}
if ($object instanceof ConfigurationAwareInterface) {
$object->setConfiguration($this->config->reader());
}
}
/**
* @deprecated Instantiate the environment and add the extension yourself
*
* @param array<string, mixed> $config
*/
public static function createCommonMarkEnvironment(array $config = []): Environment
{
$environment = new self($config);
$environment->addExtension(new CommonMarkCoreExtension());
return $environment;
}
/**
* @deprecated Instantiate the environment and add the extension yourself
*
* @param array<string, mixed> $config
*/
public static function createGFMEnvironment(array $config = []): Environment
{
$environment = new self($config);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new GithubFlavoredMarkdownExtension());
return $environment;
}
public function addEventListener(string $eventClass, callable $listener, int $priority = 0): EnvironmentBuilderInterface
{
$this->assertUninitialized('Failed to add event listener.');
$this->listenerData->add(new ListenerData($eventClass, $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(object $event): object
{
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
if ($this->eventDispatcher !== null) {
return $this->eventDispatcher->dispatch($event);
}
foreach ($this->getListenersForEvent($event) as $listener) {
if ($event instanceof StoppableEventInterface && $event->isPropagationStopped()) {
return $event;
}
$listener($event);
}
return $event;
}
public function setEventDispatcher(EventDispatcherInterface $dispatcher): void
{
$this->eventDispatcher = $dispatcher;
}
/**
* {@inheritDoc}
*
* @return iterable<callable>
*/
public function getListenersForEvent(object $event): iterable
{
foreach ($this->listenerData as $listenerData) {
\assert($listenerData instanceof ListenerData);
/** @psalm-suppress ArgumentTypeCoercion */
if (! \is_a($event, $listenerData->getEvent())) {
continue;
}
yield function (object $event) use ($listenerData) {
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
return \call_user_func($listenerData->getListener(), $event);
};
}
}
/**
* @return iterable<InlineParserInterface>
*/
public function getInlineParsers(): iterable
{
if (! $this->extensionsInitialized) {
$this->initializeExtensions();
}
return $this->inlineParsers->getIterator();
}
public function getSlugNormalizer(): TextNormalizerInterface
{
if ($this->slugNormalizer === null) {
$normalizer = $this->config->get('slug_normalizer/instance');
\assert($normalizer instanceof TextNormalizerInterface);
$this->injectEnvironmentAndConfigurationIfNeeded($normalizer);
if ($this->config->get('slug_normalizer/unique') !== UniqueSlugNormalizerInterface::DISABLED && ! $normalizer instanceof UniqueSlugNormalizer) {
$normalizer = new UniqueSlugNormalizer($normalizer);
}
if ($normalizer instanceof UniqueSlugNormalizer) {
if ($this->config->get('slug_normalizer/unique') === UniqueSlugNormalizerInterface::PER_DOCUMENT) {
$this->addEventListener(DocumentParsedEvent::class, [$normalizer, 'clearHistory'], -1000);
}
}
$this->slugNormalizer = $normalizer;
}
return $this->slugNormalizer;
}
/**
* @throws \RuntimeException
*/
private function assertUninitialized(string $message): void
{
if ($this->extensionsInitialized) {
throw new \RuntimeException($message . ' Extensions have already been initialized.');
}
}
public static function createDefaultConfiguration(): Configuration
{
return new Configuration([
'html_input' => Expect::anyOf(HtmlFilter::STRIP, HtmlFilter::ALLOW, HtmlFilter::ESCAPE)->default(HtmlFilter::ALLOW),
'allow_unsafe_links' => Expect::bool(true),
'max_nesting_level' => Expect::type('int')->default(PHP_INT_MAX),
'renderer' => Expect::structure([
'block_separator' => Expect::string("\n"),
'inner_separator' => Expect::string("\n"),
'soft_break' => Expect::string("\n"),
]),
'slug_normalizer' => Expect::structure([
'instance' => Expect::type(TextNormalizerInterface::class)->default(new SlugNormalizer()),
'max_length' => Expect::int()->min(0)->default(255),
'unique' => Expect::anyOf(UniqueSlugNormalizerInterface::DISABLED, UniqueSlugNormalizerInterface::PER_ENVIRONMENT, UniqueSlugNormalizerInterface::PER_DOCUMENT)->default(UniqueSlugNormalizerInterface::PER_DOCUMENT),
]),
]);
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -9,14 +11,9 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark;
namespace League\CommonMark\Environment;
interface EnvironmentAwareInterface
{
/**
* @param EnvironmentInterface $environment
*
* @return void
*/
public function setEnvironment(EnvironmentInterface $environment);
public function setEnvironment(EnvironmentInterface $environment): void;
}

View File

@@ -0,0 +1,84 @@
<?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\Environment;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Node\Node;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\Config\ConfigurationProviderInterface;
/**
* Interface for building the Environment with any extensions, parsers, listeners, etc. that it may need
*/
interface EnvironmentBuilderInterface extends ConfigurationProviderInterface
{
/**
* Registers the given extension with the Environment
*/
public function addExtension(ExtensionInterface $extension): EnvironmentBuilderInterface;
/**
* Registers the given block start parser with the Environment
*
* @param BlockStartParserInterface $parser Block parser instance
* @param int $priority Priority (a higher number will be executed earlier)
*
* @return $this
*/
public function addBlockStartParser(BlockStartParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface;
/**
* 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 $this
*/
public function addInlineParser(InlineParserInterface $parser, int $priority = 0): EnvironmentBuilderInterface;
/**
* Registers the given delimiter processor with the Environment
*
* @param DelimiterProcessorInterface $processor Delimiter processors instance
*/
public function addDelimiterProcessor(DelimiterProcessorInterface $processor): EnvironmentBuilderInterface;
/**
* Registers the given node renderer with the Environment
*
* @param string $nodeClass The fully-qualified node element class name the renderer below should handle
* @param NodeRendererInterface $renderer The renderer responsible for rendering the type of element given above
* @param int $priority Priority (a higher number will be executed earlier)
*
* @psalm-param class-string<Node> $nodeClass
*
* @return $this
*/
public function addRenderer(string $nodeClass, NodeRendererInterface $renderer, int $priority = 0): EnvironmentBuilderInterface;
/**
* Registers the given event listener
*
* @param class-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 $this
*/
public function addEventListener(string $eventClass, callable $listener, int $priority = 0): EnvironmentBuilderInterface;
}

View File

@@ -0,0 +1,55 @@
<?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\Environment;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorCollection;
use League\CommonMark\Extension\ExtensionInterface;
use League\CommonMark\Node\Node;
use League\CommonMark\Normalizer\TextNormalizerInterface;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\Config\ConfigurationProviderInterface;
use Psr\EventDispatcher\EventDispatcherInterface;
interface EnvironmentInterface extends ConfigurationProviderInterface, EventDispatcherInterface
{
/**
* Get all registered extensions
*
* @return ExtensionInterface[]
*/
public function getExtensions(): iterable;
/**
* @return iterable<BlockStartParserInterface>
*/
public function getBlockStartParsers(): iterable;
/**
* @return iterable<InlineParserInterface>
*/
public function getInlineParsers(): iterable;
public function getDelimiterProcessors(): DelimiterProcessorCollection;
/**
* @psalm-param class-string<Node> $nodeClass
*
* @return iterable<NodeRendererInterface>
*/
public function getRenderersForClass(string $nodeClass): iterable;
public function getSlugNormalizer(): TextNormalizerInterface;
}

View File

@@ -1,83 +0,0 @@
<?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

@@ -1,6 +1,8 @@
<?php
/**
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
* (c) Colin O'Dell <colinodell@gmail.com>
@@ -14,6 +16,8 @@
namespace League\CommonMark\Event;
use Psr\EventDispatcher\StoppableEventInterface;
/**
* Base class for classes containing event data.
*
@@ -23,10 +27,10 @@ namespace League\CommonMark\Event;
* You can call the method stopPropagation() to abort the execution of
* further listeners in your event listener.
*/
abstract class AbstractEvent
abstract class AbstractEvent implements StoppableEventInterface
{
/** @var bool */
private $propagationStopped = false;
/** @psalm-readonly-allow-private-mutation */
private bool $propagationStopped = false;
/**
* Returns whether further event listeners should be triggered.

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -11,15 +13,15 @@
namespace League\CommonMark\Event;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Node\Block\Document;
/**
* Event dispatched when the document has been fully parsed
*/
final class DocumentParsedEvent extends AbstractEvent
{
/** @var Document */
private $document;
/** @psalm-readonly */
private Document $document;
public function __construct(Document $document)
{

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -11,19 +13,18 @@
namespace League\CommonMark\Event;
use League\CommonMark\Block\Element\Document;
use League\CommonMark\Input\MarkdownInputInterface;
use League\CommonMark\Node\Block\Document;
/**
* Event dispatched when the document is about to be parsed
*/
final class DocumentPreParsedEvent extends AbstractEvent
{
/** @var Document */
private $document;
/** @psalm-readonly */
private Document $document;
/** @var MarkdownInputInterface */
private $markdown;
private MarkdownInputInterface $markdown;
public function __construct(Document $document, MarkdownInputInterface $markdown)
{

View File

@@ -0,0 +1,44 @@
<?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\Event;
use League\CommonMark\Node\Block\Document;
/**
* Event dispatched just before rendering begins
*/
final class DocumentPreRenderEvent extends AbstractEvent
{
/** @psalm-readonly */
private Document $document;
/** @psalm-readonly */
private string $format;
public function __construct(Document $document, string $format)
{
$this->document = $document;
$this->format = $format;
}
public function getDocument(): Document
{
return $this->document;
}
public function getFormat(): string
{
return $this->format;
}
}

View File

@@ -0,0 +1,42 @@
<?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.
*/
declare(strict_types=1);
namespace League\CommonMark\Event;
use League\CommonMark\Output\RenderedContentInterface;
final class DocumentRenderedEvent extends AbstractEvent
{
private RenderedContentInterface $output;
public function __construct(RenderedContentInterface $output)
{
$this->output = $output;
}
/**
* @psalm-mutation-free
*/
public function getOutput(): RenderedContentInterface
{
return $this->output;
}
/**
* @psalm-external-mutation-free
*/
public function replaceOutput(RenderedContentInterface $output): void
{
$this->output = $output;
}
}

View File

@@ -0,0 +1,50 @@
<?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\Event;
/**
* @internal
*
* @psalm-immutable
*/
final class ListenerData
{
/** @var class-string */
private string $event;
/** @var callable */
private $listener;
/**
* @param class-string $event
*/
public function __construct(string $event, callable $listener)
{
$this->event = $event;
$this->listener = $listener;
}
/**
* @return class-string
*/
public function getEvent(): string
{
return $this->event;
}
public function getListener(): callable
{
return $this->listener;
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*

View File

@@ -14,18 +14,18 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
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\AttributesBlockStartParser;
use League\CommonMark\Extension\Attributes\Parser\AttributesInlineParser;
use League\CommonMark\Extension\ExtensionInterface;
final class AttributesExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addBlockParser(new AttributesBlockParser());
$environment->addBlockStartParser(new AttributesBlockStartParser());
$environment->addInlineParser(new AttributesInlineParser());
$environment->addEventListener(DocumentParsedEvent::class, [new AttributesListener(), 'processDocument']);
}

View File

@@ -14,15 +14,14 @@ 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\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Block\ListBlock;
use League\CommonMark\Extension\CommonMark\Node\Block\ListItem;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Node;
final class AttributesListener
@@ -32,16 +31,14 @@ final class AttributesListener
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)) {
foreach ($event->getDocument()->iterator() as $node) {
if (! ($node instanceof Attributes || $node instanceof AttributesInline)) {
continue;
}
[$target, $direction] = self::findTargetAndDirection($node);
if ($target instanceof AbstractBlock || $target instanceof AbstractInline) {
if ($target instanceof Node) {
$parent = $target->parent();
if ($parent instanceof ListItem && $parent->parent() instanceof ListBlock && $parent->parent()->isTight()) {
$target = $parent;
@@ -53,14 +50,7 @@ final class AttributesListener
$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);
}
$target->data->set('attributes', $attributes);
}
$node->detach();
@@ -68,22 +58,22 @@ final class AttributesListener
}
/**
* @param Node $node
* @param Attributes|AttributesInline $node
*
* @return array<Node|string|null>
*/
private static function findTargetAndDirection(Node $node): array
private static function findTargetAndDirection($node): array
{
$target = null;
$target = null;
$direction = null;
$previous = $next = $node;
$previous = $next = $node;
while (true) {
$previous = self::getPrevious($previous);
$next = self::getNext($next);
$next = self::getNext($next);
if ($previous === null && $next === null) {
if (!$node->parent() instanceof FencedCode) {
$target = $node->parent();
if (! $node->parent() instanceof FencedCode) {
$target = $node->parent();
$direction = self::DIRECTION_SUFFIX;
}
@@ -94,15 +84,15 @@ final class AttributesListener
continue;
}
if ($previous !== null && !self::isAttributesNode($previous)) {
$target = $previous;
if ($previous !== null && ! self::isAttributesNode($previous)) {
$target = $previous;
$direction = self::DIRECTION_SUFFIX;
break;
}
if ($next !== null && !self::isAttributesNode($next)) {
$target = $next;
if ($next !== null && ! self::isAttributesNode($next)) {
$target = $next;
$direction = self::DIRECTION_PREFIX;
break;
@@ -112,26 +102,34 @@ final class AttributesListener
return [$target, $direction];
}
/**
* Get any previous block (sibling or parent) this might apply to
*/
private static function getPrevious(?Node $node = null): ?Node
{
$previous = $node instanceof Node ? $node->previous() : null;
if ($node instanceof Attributes) {
if ($node->getTarget() === Attributes::TARGET_NEXT) {
return null;
}
if ($previous instanceof AbstractBlock && $previous->endsWithBlankLine()) {
$previous = null;
if ($node->getTarget() === Attributes::TARGET_PARENT) {
return $node->parent();
}
}
return $previous;
return $node instanceof Node ? $node->previous() : null;
}
/**
* Get any previous block (sibling or parent) this might apply to
*/
private static function getNext(?Node $node = null): ?Node
{
$next = $node instanceof Node ? $node->next() : null;
if ($node instanceof AbstractBlock && $node->endsWithBlankLine()) {
$next = null;
if ($node instanceof Attributes && $node->getTarget() !== Attributes::TARGET_NEXT) {
return null;
}
return $next;
return $node instanceof Node ? $node->next() : null;
}
private static function isAttributesNode(Node $node): bool

View File

@@ -14,19 +14,26 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Node;
use League\CommonMark\Block\Element\AbstractBlock;
use League\CommonMark\Cursor;
use League\CommonMark\Node\Block\AbstractBlock;
final class Attributes extends AbstractBlock
{
public const TARGET_PARENT = 0;
public const TARGET_PREVIOUS = 1;
public const TARGET_NEXT = 2;
/** @var array<string, mixed> */
private $attributes;
private array $attributes;
private int $target = self::TARGET_NEXT;
/**
* @param array<string, mixed> $attributes
*/
public function __construct(array $attributes)
{
parent::__construct();
$this->attributes = $attributes;
}
@@ -38,25 +45,21 @@ final class Attributes extends AbstractBlock
return $this->attributes;
}
public function canContain(AbstractBlock $block): bool
/**
* @param array<string, mixed> $attributes
*/
public function setAttributes(array $attributes): void
{
return false;
$this->attributes = $attributes;
}
public function isCode(): bool
public function getTarget(): int
{
return false;
return $this->target;
}
public function matchesNextLine(Cursor $cursor): bool
public function setTarget(int $target): void
{
$this->setLastLineBlank($cursor->isBlank());
return false;
}
public function shouldLastLineBeBlank(Cursor $cursor, int $currentLineNumber): bool
{
return false;
$this->target = $target;
}
}

View File

@@ -14,25 +14,24 @@ declare(strict_types=1);
namespace League\CommonMark\Extension\Attributes\Node;
use League\CommonMark\Inline\Element\AbstractInline;
use League\CommonMark\Node\Inline\AbstractInline;
final class AttributesInline extends AbstractInline
{
/** @var array<string, mixed> */
public $attributes;
private array $attributes;
/** @var bool */
public $block;
private bool $block;
/**
* @param array<string, mixed> $attributes
* @param bool $block
*/
public function __construct(array $attributes, bool $block)
{
parent::__construct();
$this->attributes = $attributes;
$this->block = $block;
$this->data = ['delim' => true]; // TODO: Re-implement as a delimiter?
$this->block = $block;
}
/**
@@ -43,6 +42,14 @@ final class AttributesInline extends AbstractInline
return $this->attributes;
}
/**
* @param array<string, mixed> $attributes
*/
public function setAttributes(array $attributes): void
{
$this->attributes = $attributes;
}
public function isBlock(): bool
{
return $this->block;

View File

@@ -0,0 +1,92 @@
<?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\Attributes;
use League\CommonMark\Extension\Attributes\Util\AttributesHelper;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Parser\Block\AbstractBlockContinueParser;
use League\CommonMark\Parser\Block\BlockContinue;
use League\CommonMark\Parser\Block\BlockContinueParserInterface;
use League\CommonMark\Parser\Cursor;
final class AttributesBlockContinueParser extends AbstractBlockContinueParser
{
private Attributes $block;
private AbstractBlock $container;
private bool $hasSubsequentLine = false;
/**
* @param array<string, mixed> $attributes The attributes identified by the block start parser
* @param AbstractBlock $container The node we were in when these attributes were discovered
*/
public function __construct(array $attributes, AbstractBlock $container)
{
$this->block = new Attributes($attributes);
$this->container = $container;
}
public function getBlock(): AbstractBlock
{
return $this->block;
}
public function tryContinue(Cursor $cursor, BlockContinueParserInterface $activeBlockParser): ?BlockContinue
{
$this->hasSubsequentLine = true;
$cursor->advanceToNextNonSpaceOrTab();
// Does this next line also have attributes?
$attributes = AttributesHelper::parseAttributes($cursor);
$cursor->advanceToNextNonSpaceOrTab();
if ($cursor->isAtEnd() && $attributes !== []) {
// It does! Merge them into what we parsed previously
$this->block->setAttributes(AttributesHelper::mergeAttributes(
$this->block->getAttributes(),
$attributes
));
// Tell the core parser we've consumed everything
return BlockContinue::at($cursor);
}
// Okay, so there are no attributes on the next line
// If this next line is blank we know we can't target the next node, it must be a previous one
if ($cursor->isBlank()) {
$this->block->setTarget(Attributes::TARGET_PREVIOUS);
}
return BlockContinue::none();
}
public function closeBlock(): void
{
// Attributes appearing at the very end of the document won't have any last lines to check
// so we can make that determination here
if (! $this->hasSubsequentLine) {
$this->block->setTarget(Attributes::TARGET_PREVIOUS);
}
// We know this block must apply to the "previous" block, but that could be a sibling or parent,
// so we check the containing block to see which one it might be.
if ($this->block->getTarget() === Attributes::TARGET_PREVIOUS && $this->block->parent() === $this->container) {
$this->block->setTarget(Attributes::TARGET_PARENT);
}
}
}

View File

@@ -1,44 +0,0 @@
<?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,40 @@
<?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\Util\AttributesHelper;
use League\CommonMark\Parser\Block\BlockStart;
use League\CommonMark\Parser\Block\BlockStartParserInterface;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Parser\MarkdownParserStateInterface;
final class AttributesBlockStartParser implements BlockStartParserInterface
{
public function tryStart(Cursor $cursor, MarkdownParserStateInterface $parserState): ?BlockStart
{
$originalPosition = $cursor->getPosition();
$attributes = AttributesHelper::parseAttributes($cursor);
if ($attributes === [] && $originalPosition === $cursor->getPosition()) {
return BlockStart::none();
}
if ($cursor->getNextNonSpaceCharacter() !== null) {
return BlockStart::none();
}
return BlockStart::of(new AttributesBlockContinueParser($attributes, $parserState->getActiveBlockParser()->getBlock()))->at($cursor);
}
}

View File

@@ -16,33 +16,30 @@ 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;
use League\CommonMark\Node\StringContainerInterface;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class AttributesInlineParser implements InlineParserInterface
{
/**
* {@inheritdoc}
*/
public function getCharacters(): array
public function getMatchDefinition(): InlineParserMatch
{
return ['{'];
return InlineParserMatch::string('{');
}
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
$char = (string) $cursor->peek(-1);
$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 === ' ' && ($prev = $inlineContext->getContainer()->lastChild()) instanceof StringContainerInterface) {
$prev->setLiteral(\rtrim($prev->getLiteral(), ' '));
}
if ($char === '') {

View File

@@ -14,9 +14,8 @@ 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\Node\Node;
use League\CommonMark\Parser\Cursor;
use League\CommonMark\Util\RegexHelper;
/**
@@ -24,29 +23,43 @@ use League\CommonMark\Util\RegexHelper;
*/
final class AttributesHelper
{
private const SINGLE_ATTRIBUTE = '\s*([.#][_a-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')\s*';
private const ATTRIBUTE_LIST = '/^{:?(' . self::SINGLE_ATTRIBUTE . ')+}/i';
/**
* @param Cursor $cursor
*
* @return array<string, mixed>
*/
public static function parseAttributes(Cursor $cursor): array
{
$state = $cursor->saveState();
$cursor->advanceToNextNonSpaceOrNewline();
// Quick check to see if we might have attributes
if ($cursor->getCharacter() !== '{') {
$cursor->restoreState($state);
return [];
}
$cursor->advanceBy(1);
if ($cursor->getCharacter() === ':') {
$cursor->advanceBy(1);
// Attempt to match the entire attribute list expression
// While this is less performant than checking for '{' now and '}' later, it simplifies
// matching individual attributes since they won't need to look ahead for the closing '}'
// while dealing with the fact that attributes can technically contain curly braces.
// So we'll just match the start and end braces up front.
$attributeExpression = $cursor->match(self::ATTRIBUTE_LIST);
if ($attributeExpression === null) {
$cursor->restoreState($state);
return [];
}
// Trim the leading '{' or '{:' and the trailing '}'
$attributeExpression = \ltrim(\substr($attributeExpression, 1, -1), ':');
$attributeCursor = new Cursor($attributeExpression);
/** @var array<string, mixed> $attributes */
$attributes = [];
$regex = '/^\s*([.#][_a-z0-9-]+|' . RegexHelper::PARTIAL_ATTRIBUTENAME . RegexHelper::PARTIAL_ATTRIBUTEVALUESPEC . ')(?<!})\s*/i';
while ($attribute = \trim((string) $cursor->match($regex))) {
while ($attribute = \trim((string) $attributeCursor->match('/^' . self::SINGLE_ATTRIBUTE . '/i'))) {
if ($attribute[0] === '#') {
$attributes['id'] = \substr($attribute, 1);
@@ -60,9 +73,10 @@ final class AttributesHelper
}
[$name, $value] = \explode('=', $attribute, 2);
$first = $value[0];
$last = \substr($value, -1);
if ((($first === '"' && $last === '"') || ($first === "'" && $last === "'")) && \strlen($value) > 1) {
$last = \substr($value, -1);
if (($first === '"' && $last === '"') || ($first === "'" && $last === "'") && \strlen($value) > 1) {
$value = \substr($value, 1, -1);
}
@@ -71,22 +85,10 @@ final class AttributesHelper
$attributes['class'][] = $class;
}
} else {
$attributes[trim($name)] = trim($value);
$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']);
}
@@ -95,8 +97,8 @@ final class AttributesHelper
}
/**
* @param AbstractBlock|AbstractInline|array<string, mixed> $attributes1
* @param AbstractBlock|AbstractInline|array<string, mixed> $attributes2
* @param Node|array<string, mixed> $attributes1
* @param Node|array<string, mixed> $attributes2
*
* @return array<string, mixed>
*/
@@ -104,14 +106,18 @@ final class AttributesHelper
{
$attributes = [];
foreach ([$attributes1, $attributes2] as $arg) {
if ($arg instanceof AbstractBlock || $arg instanceof AbstractInline) {
$arg = $arg->data['attributes'] ?? [];
if ($arg instanceof Node) {
$arg = $arg->data->get('attributes');
}
/** @var array<string, mixed> $arg */
$arg = (array) $arg;
if (isset($arg['class'])) {
foreach (\array_filter(\explode(' ', \trim($arg['class']))) as $class) {
if (\is_string($arg['class'])) {
$arg['class'] = \array_filter(\explode(' ', \trim($arg['class'])));
}
foreach ($arg['class'] as $class) {
$attributes['class'][] = $class;
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -11,15 +13,14 @@
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\ConfigurableEnvironmentInterface;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\ExtensionInterface;
final class AutolinkExtension implements ExtensionInterface
{
public function register(ConfigurableEnvironmentInterface $environment)
public function register(EnvironmentBuilderInterface $environment): void
{
$environment->addEventListener(DocumentParsedEvent::class, new EmailAutolinkProcessor());
$environment->addEventListener(DocumentParsedEvent::class, new UrlAutolinkProcessor());
$environment->addInlineParser(new EmailAutolinkParser());
$environment->addInlineParser(new UrlAutolinkParser());
}
}

View File

@@ -0,0 +1,48 @@
<?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\Extension\Autolink;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class EmailAutolinkParser implements InlineParserInterface
{
private const REGEX = '[A-Za-z0-9.\-_+]+@[A-Za-z0-9\-_]+\.[A-Za-z0-9\-_.]+';
public function getMatchDefinition(): InlineParserMatch
{
return InlineParserMatch::regex(self::REGEX);
}
public function parse(InlineParserContext $inlineContext): bool
{
$email = $inlineContext->getFullMatch();
// The last character cannot be - or _
if (\in_array(\substr($email, -1), ['-', '_'], true)) {
return false;
}
// Does the URL end with punctuation that should be stripped?
if (\substr($email, -1) === '.') {
$email = \substr($email, 0, -1);
}
$inlineContext->getCursor()->advanceBy(\strlen($email));
$inlineContext->getContainer()->appendChild(new Link('mailto:' . $email, $email));
return true;
}
}

View File

@@ -1,78 +0,0 @@
<?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

@@ -1,96 +0,0 @@
<?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

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -11,15 +13,17 @@
namespace League\CommonMark\Extension\Autolink;
use League\CommonMark\Event\DocumentParsedEvent;
use League\CommonMark\Inline\Element\Link;
use League\CommonMark\Inline\Element\Text;
use League\CommonMark\Extension\CommonMark\Node\Inline\Link;
use League\CommonMark\Parser\Inline\InlineParserInterface;
use League\CommonMark\Parser\Inline\InlineParserMatch;
use League\CommonMark\Parser\InlineParserContext;
final class UrlAutolinkProcessor
final class UrlAutolinkParser implements InlineParserInterface
{
private const ALLOWED_AFTER = [null, ' ', "\t", "\n", "\x0b", "\x0c", "\x0d", '*', '_', '~', '('];
// 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
private const REGEX = '~
(
# Must start with a supported scheme + auth, or "www"
(?:
@@ -41,8 +45,15 @@ final class UrlAutolinkProcessor
(?:\# (?:[\pL\pN\-._\~!$&\'()*+,;=:@/?]|%%[0-9A-Fa-f]{2})* )? # a fragment (optional)
)~ixu';
/** @var string */
private $finalRegex;
/**
* @var string[]
*
* @psalm-readonly
*/
private array $prefixes = ['www'];
/** @psalm-readonly */
private string $finalRegex;
/**
* @param array<int, string> $allowedProtocols
@@ -50,90 +61,66 @@ final class UrlAutolinkProcessor
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);
}
foreach ($allowedProtocols as $protocol) {
$this->prefixes[] = $protocol . '://';
}
}
private static function processAutolinks(Text $node, string $regex): void
public function getMatchDefinition(): InlineParserMatch
{
$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();
return InlineParserMatch::oneOf(...$this->prefixes);
}
private static function addLink(Text $node, string $url): void
public function parse(InlineParserContext $inlineContext): bool
{
$cursor = $inlineContext->getCursor();
// Autolinks can only come at the beginning of a line, after whitespace, or certain delimiting characters
$previousChar = $cursor->peek(-1);
if (! \in_array($previousChar, self::ALLOWED_AFTER, true)) {
return false;
}
// Check if we have a valid URL
if (! \preg_match($this->finalRegex, $cursor->getRemainder(), $matches)) {
return false;
}
$url = $matches[0];
// Does the URL end with punctuation that should be stripped?
if (\preg_match('/(.+?)([?!.,:*_~]+)$/', $url, $matches)) {
// Add the punctuation later
$url = $matches[1];
}
// Does the URL end with something that looks like an entity reference?
if (\preg_match('/(.+)(&[A-Za-z0-9]+;)$/', $url, $matches)) {
$url = $matches[1];
}
// Does the URL need unmatched parens chopped off?
if (\substr($url, -1) === ')' && ($diff = self::diffParens($url)) > 0) {
$url = \substr($url, 0, -$diff);
}
$cursor->advanceBy(\mb_strlen($url, 'UTF-8'));
// Auto-prefix 'http://' onto 'www' URLs
if (\substr($url, 0, 4) === 'www.') {
$node->insertBefore(new Link('http://' . $url, $url));
$inlineContext->getContainer()->appendChild(new Link('http://' . $url, $url));
return;
return true;
}
$node->insertBefore(new Link($url, $url));
$inlineContext->getContainer()->appendChild(new Link($url, $url));
return true;
}
/**
* @param string $content
*
* @return int
* @psalm-pure
*/
private static function diffParens(string $content): int
{

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark;
use League\CommonMark\Environment\EnvironmentBuilderInterface;
use League\CommonMark\Extension\CommonMark\Delimiter\Processor\EmphasisDelimiterProcessor;
use League\CommonMark\Extension\ConfigurableExtensionInterface;
use League\CommonMark\Node as CoreNode;
use League\CommonMark\Parser as CoreParser;
use League\CommonMark\Renderer as CoreRenderer;
use League\Config\ConfigurationBuilderInterface;
use Nette\Schema\Expect;
final class CommonMarkCoreExtension implements ConfigurableExtensionInterface
{
public function configureSchema(ConfigurationBuilderInterface $builder): void
{
$builder->addSchema('commonmark', Expect::structure([
'use_asterisk' => Expect::bool(true),
'use_underscore' => Expect::bool(true),
'enable_strong' => Expect::bool(true),
'enable_em' => Expect::bool(true),
'unordered_list_markers' => Expect::listOf('string')->min(1)->default(['*', '+', '-'])->mergeDefaults(false),
]));
}
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing.TooMuchSpaceAfterComma,Squiz.WhiteSpace.SemicolonSpacing.Incorrect
public function register(EnvironmentBuilderInterface $environment): void
{
$environment
->addBlockStartParser(new Parser\Block\BlockQuoteStartParser(), 70)
->addBlockStartParser(new Parser\Block\HeadingStartParser(), 60)
->addBlockStartParser(new Parser\Block\FencedCodeStartParser(), 50)
->addBlockStartParser(new Parser\Block\HtmlBlockStartParser(), 40)
->addBlockStartParser(new Parser\Block\ThematicBreakStartParser(), 20)
->addBlockStartParser(new Parser\Block\ListBlockStartParser(), 10)
->addBlockStartParser(new Parser\Block\IndentedCodeStartParser(), -100)
->addInlineParser(new CoreParser\Inline\NewlineParser(), 200)
->addInlineParser(new Parser\Inline\BacktickParser(), 150)
->addInlineParser(new Parser\Inline\EscapableParser(), 80)
->addInlineParser(new Parser\Inline\EntityParser(), 70)
->addInlineParser(new Parser\Inline\AutolinkParser(), 50)
->addInlineParser(new Parser\Inline\HtmlInlineParser(), 40)
->addInlineParser(new Parser\Inline\CloseBracketParser(), 30)
->addInlineParser(new Parser\Inline\OpenBracketParser(), 20)
->addInlineParser(new Parser\Inline\BangParser(), 10)
->addRenderer(Node\Block\BlockQuote::class, new Renderer\Block\BlockQuoteRenderer(), 0)
->addRenderer(CoreNode\Block\Document::class, new CoreRenderer\Block\DocumentRenderer(), 0)
->addRenderer(Node\Block\FencedCode::class, new Renderer\Block\FencedCodeRenderer(), 0)
->addRenderer(Node\Block\Heading::class, new Renderer\Block\HeadingRenderer(), 0)
->addRenderer(Node\Block\HtmlBlock::class, new Renderer\Block\HtmlBlockRenderer(), 0)
->addRenderer(Node\Block\IndentedCode::class, new Renderer\Block\IndentedCodeRenderer(), 0)
->addRenderer(Node\Block\ListBlock::class, new Renderer\Block\ListBlockRenderer(), 0)
->addRenderer(Node\Block\ListItem::class, new Renderer\Block\ListItemRenderer(), 0)
->addRenderer(CoreNode\Block\Paragraph::class, new CoreRenderer\Block\ParagraphRenderer(), 0)
->addRenderer(Node\Block\ThematicBreak::class, new Renderer\Block\ThematicBreakRenderer(), 0)
->addRenderer(Node\Inline\Code::class, new Renderer\Inline\CodeRenderer(), 0)
->addRenderer(Node\Inline\Emphasis::class, new Renderer\Inline\EmphasisRenderer(), 0)
->addRenderer(Node\Inline\HtmlInline::class, new Renderer\Inline\HtmlInlineRenderer(), 0)
->addRenderer(Node\Inline\Image::class, new Renderer\Inline\ImageRenderer(), 0)
->addRenderer(Node\Inline\Link::class, new Renderer\Inline\LinkRenderer(), 0)
->addRenderer(CoreNode\Inline\Newline::class, new CoreRenderer\Inline\NewlineRenderer(), 0)
->addRenderer(Node\Inline\Strong::class, new Renderer\Inline\StrongRenderer(), 0)
->addRenderer(CoreNode\Inline\Text::class, new CoreRenderer\Inline\TextRenderer(), 0)
;
if ($environment->getConfiguration()->get('commonmark/use_asterisk')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('*'));
}
if ($environment->getConfiguration()->get('commonmark/use_underscore')) {
$environment->addDelimiterProcessor(new EmphasisDelimiterProcessor('_'));
}
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -15,22 +17,23 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Delimiter\Processor;
namespace League\CommonMark\Extension\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;
use League\CommonMark\Delimiter\Processor\DelimiterProcessorInterface;
use League\CommonMark\Extension\CommonMark\Node\Inline\Emphasis;
use League\CommonMark\Extension\CommonMark\Node\Inline\Strong;
use League\CommonMark\Node\Inline\AbstractStringContainer;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;
final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, ConfigurationAwareInterface
{
/** @var string */
private $char;
/** @psalm-readonly */
private string $char;
/** @var ConfigurationInterface|null */
private $config;
/** @psalm-readonly-allow-private-mutation */
private ConfigurationInterface $config;
/**
* @param string $char The emphasis character to use (typically '*' or '_')
@@ -64,26 +67,26 @@ final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, C
// Calculate actual number of delimiters used from this closer
if ($opener->getLength() >= 2 && $closer->getLength() >= 2) {
if ($this->enableStrong()) {
if ($this->config->get('commonmark/enable_strong')) {
return 2;
}
return 0;
}
if ($this->enableEm()) {
if ($this->config->get('commonmark/enable_em')) {
return 1;
}
return 0;
}
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse)
public function process(AbstractStringContainer $opener, AbstractStringContainer $closer, int $delimiterUse): void
{
if ($delimiterUse === 1) {
$emphasis = new Emphasis();
$emphasis = new Emphasis($this->char);
} elseif ($delimiterUse === 2) {
$emphasis = new Strong();
$emphasis = new Strong($this->char . $this->char);
} else {
return;
}
@@ -98,40 +101,8 @@ final class EmphasisDelimiterProcessor implements DelimiterProcessorInterface, C
$opener->insertAfter($emphasis);
}
public function setConfiguration(ConfigurationInterface $configuration)
public function setConfiguration(ConfigurationInterface $configuration): void
{
$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,20 @@
<?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\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class BlockQuote extends AbstractBlock
{
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\StringContainerInterface;
final class FencedCode extends AbstractBlock implements StringContainerInterface
{
private ?string $info = null;
private string $literal = '';
private int $length;
private string $char;
private int $offset;
public function __construct(int $length, string $char, int $offset)
{
parent::__construct();
$this->length = $length;
$this->char = $char;
$this->offset = $offset;
}
public function getInfo(): ?string
{
return $this->info;
}
/**
* @return string[]
*/
public function getInfoWords(): array
{
return \preg_split('/\s+/', $this->info ?? '') ?: [];
}
public function setInfo(string $info): void
{
$this->info = $info;
}
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
public function getChar(): string
{
return $this->char;
}
public function setChar(string $char): void
{
$this->char = $char;
}
public function getLength(): int
{
return $this->length;
}
public function setLength(int $length): void
{
$this->length = $length;
}
public function getOffset(): int
{
return $this->offset;
}
public function setOffset(int $offset): void
{
$this->offset = $offset;
}
}

View File

@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
final class Heading extends AbstractBlock
{
private int $level;
public function __construct(int $level)
{
parent::__construct();
$this->level = $level;
}
public function getLevel(): int
{
return $this->level;
}
public function setLevel(int $level): void
{
$this->level = $level;
}
}

View File

@@ -0,0 +1,79 @@
<?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\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\RawMarkupContainerInterface;
final class HtmlBlock extends AbstractBlock implements RawMarkupContainerInterface
{
// Any changes to these constants should be reflected in .phpstorm.meta.php
public const TYPE_1_CODE_CONTAINER = 1;
public const TYPE_2_COMMENT = 2;
public const TYPE_3 = 3;
public const TYPE_4 = 4;
public const TYPE_5_CDATA = 5;
public const TYPE_6_BLOCK_ELEMENT = 6;
public const TYPE_7_MISC_ELEMENT = 7;
/**
* @psalm-var self::TYPE_* $type
* @phpstan-var self::TYPE_* $type
*/
private int $type;
private string $literal = '';
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function __construct(int $type)
{
parent::__construct();
$this->type = $type;
}
/**
* @psalm-return self::TYPE_*
*
* @phpstan-return self::TYPE_*
*/
public function getType(): int
{
return $this->type;
}
/**
* @psalm-param self::TYPE_* $type
*
* @phpstan-param self::TYPE_* $type
*/
public function setType(int $type): void
{
$this->type = $type;
}
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
}

View File

@@ -0,0 +1,32 @@
<?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\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\StringContainerInterface;
final class IndentedCode extends AbstractBlock implements StringContainerInterface
{
private string $literal = '';
public function getLiteral(): string
{
return $this->literal;
}
public function setLiteral(string $literal): void
{
$this->literal = $literal;
}
}

View File

@@ -0,0 +1,56 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
use League\CommonMark\Node\Block\TightBlockInterface;
class ListBlock extends AbstractBlock implements TightBlockInterface
{
public const TYPE_BULLET = 'bullet';
public const TYPE_ORDERED = 'ordered';
public const DELIM_PERIOD = 'period';
public const DELIM_PAREN = 'paren';
protected bool $tight = false;
/** @psalm-readonly */
protected ListData $listData;
public function __construct(ListData $listData)
{
parent::__construct();
$this->listData = $listData;
}
public function getListData(): ListData
{
return $this->listData;
}
public function isTight(): bool
{
return $this->tight;
}
public function setTight(bool $tight): void
{
$this->tight = $tight;
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -12,46 +14,31 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
namespace League\CommonMark\Extension\CommonMark\Node\Block;
class ListData
{
/**
* @var int|null
*/
public $start;
public ?int $start = null;
public int $padding = 0;
/**
* @var int
* @psalm-var ListBlock::TYPE_*
* @phpstan-var ListBlock::TYPE_*
*/
public $padding = 0;
public string $type;
/**
* @var string
* @psalm-var ListBlock::DELIM_*|null
* @phpstan-var ListBlock::DELIM_*|null
*/
public $type;
public ?string $delimiter = null;
/**
* @var string|null
*/
public $delimiter;
public ?string $bulletChar = null;
/**
* @var string|null
*/
public $bulletChar;
public int $markerOffset;
/**
* @var int
*/
public $markerOffset;
/**
* @param ListData $data
*
* @return bool
*/
public function equals(ListData $data)
public function equals(ListData $data): bool
{
return $this->type === $data->type &&
$this->delimiter === $data->delimiter &&

View File

@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class ListItem extends AbstractBlock
{
/** @psalm-readonly */
protected ListData $listData;
public function __construct(ListData $listData)
{
parent::__construct();
$this->listData = $listData;
}
public function getListData(): ListData
{
return $this->listData;
}
}

View File

@@ -0,0 +1,20 @@
<?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\Extension\CommonMark\Node\Block;
use League\CommonMark\Node\Block\AbstractBlock;
class ThematicBreak extends AbstractBlock
{
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -12,42 +14,28 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Inline\Element;
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
abstract class AbstractWebResource extends AbstractInline
{
/**
* @var string
*/
protected $url;
protected string $url;
public function __construct(string $url)
{
parent::__construct();
$this->url = $url;
}
/**
* @return string
*/
public function getUrl(): string
{
return $this->url;
}
/**
* @param string $url
*
* @return $this
*/
public function setUrl(string $url)
public function setUrl(string $url): void
{
$this->url = $url;
return $this;
}
public function isContainer(): bool
{
return true;
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -12,9 +14,10 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Block\Element;
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
interface InlineContainerInterface extends StringContainerInterface
use League\CommonMark\Node\Inline\AbstractStringContainer;
class Code extends AbstractStringContainer
{
public function getStringContent(): string;
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Inline\DelimitedInterface;
final class Emphasis extends AbstractInline implements DelimitedInterface
{
private string $delimiter;
public function __construct(string $delimiter = '_')
{
parent::__construct();
$this->delimiter = $delimiter;
}
public function getOpeningDelimiter(): string
{
return $this->delimiter;
}
public function getClosingDelimiter(): string
{
return $this->delimiter;
}
}

View File

@@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractStringContainer;
use League\CommonMark\Node\RawMarkupContainerInterface;
final class HtmlInline extends AbstractStringContainer implements RawMarkupContainerInterface
{
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -12,20 +14,36 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Inline\Element;
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\Text;
class Image extends AbstractWebResource
{
protected ?string $title = null;
public function __construct(string $url, ?string $label = null, ?string $title = null)
{
parent::__construct($url);
if (!empty($label)) {
if ($label !== null && $label !== '') {
$this->appendChild(new Text($label));
}
if (!empty($title)) {
$this->data['title'] = $title;
$this->title = $title;
}
public function getTitle(): ?string
{
if ($this->title === '') {
return null;
}
return $this->title;
}
public function setTitle(?string $title): void
{
$this->title = $title;
}
}

View File

@@ -1,5 +1,7 @@
<?php
declare(strict_types=1);
/*
* This file is part of the league/commonmark package.
*
@@ -12,20 +14,36 @@
* file that was distributed with this source code.
*/
namespace League\CommonMark\Inline\Element;
namespace League\CommonMark\Extension\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\Text;
class Link extends AbstractWebResource
{
protected ?string $title = null;
public function __construct(string $url, ?string $label = null, ?string $title = null)
{
parent::__construct($url);
if (!empty($label)) {
if ($label !== null && $label !== '') {
$this->appendChild(new Text($label));
}
if (!empty($title)) {
$this->data['title'] = $title;
$this->title = $title;
}
public function getTitle(): ?string
{
if ($this->title === '') {
return null;
}
return $this->title;
}
public function setTitle(?string $title): void
{
$this->title = $title;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* 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\CommonMark\Node\Inline;
use League\CommonMark\Node\Inline\AbstractInline;
use League\CommonMark\Node\Inline\DelimitedInterface;
final class Strong extends AbstractInline implements DelimitedInterface
{
private string $delimiter;
public function __construct(string $delimiter = '**')
{
parent::__construct();
$this->delimiter = $delimiter;
}
public function getOpeningDelimiter(): string
{
return $this->delimiter;
}
public function getClosingDelimiter(): string
{
return $this->delimiter;
}
}

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