Skip to content

Commit

Permalink
Merge pull request #629 from thephpleague/render-xml
Browse files Browse the repository at this point in the history
XML Rendering
  • Loading branch information
colinodell authored Jun 12, 2021
2 parents a89474f + 61b7f8c commit 4b9bf21
Show file tree
Hide file tree
Showing 228 changed files with 4,156 additions and 47 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ See <https://commonmark.thephpleague.com/2.0/upgrading/> for detailed informatio
- Added new `FrontMatterExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/front-matter/))
- Added new `DescriptionListExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/description-lists/))
- Added new `DefaultAttributesExtension` ([see documentation](https://commonmark.thephpleague.com/extensions/default-attributes/))
- Added new `XmlRenderer` ([see documentation](https://commonmark.thephpleague.com/xml/)) to simplify AST debugging (#431)
- Added `Query` class to simplify Node traversal when looking to take action on certain Nodes
- Added the ability to delegate event dispatching to PSR-14 compliant event dispatcher libraries
- Added the ability to configure disallowed raw HTML tags (#507)
Expand All @@ -37,6 +38,7 @@ See <https://commonmark.thephpleague.com/2.0/upgrading/> for detailed informatio
- `DocumentRenderedEvent`
- `EllipsesParser` (extracted from `PunctuationParser`)
- `ExpressionInterface`
- `FallbackNodeXmlRenderer`
- `InlineParserEngineInterface`
- `InlineParserMatch`
- `MarkdownParserState`
Expand All @@ -48,8 +50,11 @@ See <https://commonmark.thephpleague.com/2.0/upgrading/> for detailed informatio
- `RenderedContentInterface`
- `ReplaceUnpairedQuotesListener`
- `SpecReader`
- `TableOfContentsRenderer`
- `UniqueSlugNormalizer`
- `UniqueSlugNormalizerInterface`
- `XmlRenderer`
- `XmlNodeRendererInterface`
- Added several new methods:
- `Environment::createDefaultConfiguration()`
- `Environment::setEventDispatcher()`
Expand Down
4 changes: 4 additions & 0 deletions docs/2.0/customization/abstract-syntax-tree.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ The root node of the AST will always be a `Document` object. You can obtain thi
- By calling the `parse()` method on the `MarkdownParser`
- By calling the `getDocument()` method on either the `DocumentPreParsedEvent` or `DocumentParsedEvent` [see the (Event Dispatcher documentation](/2.0/customization/event-dispatcher/))

## Visualization

Even with an interactive debugger it can be tricky to view an entire tree at once. Consider using the [`XmlRenderer`](/2.0/xml/) to provide a simple text-based representation of the AST for debugging purposes.

## Node Traversal

There are three different ways to traverse/iterate the Nodes within the AST:
Expand Down
30 changes: 30 additions & 0 deletions docs/2.0/customization/rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,33 @@ Note that thematic breaks should not contain children, which is why the `$childR

- Return an `HtmlElement` if possible. This makes it easier to extend and modify the results later.
- Don't forget to render any child elements that your node might contain!

## XML Rendering

The [XML renderer](/2.0/xml/) will automatically attempt to convert any AST nodes to XML by inspecting the name of the block/inline node and its attributes. You can instead control the XML element name and attributes by making your renderer implement `XmlNodeRendererInterface`:

```php
use League\CommonMark\Node\Node;
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;

class TextDividerRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
public function render(Node $node, ChildNodeRendererInterface $childRenderer)
{
return new HtmlElement('pre', ['class' => 'divider'], '==============================');
}

public function getXmlTagName(Node $node): string
{
return 'text_divider';
}

public function getXmlAttributes(Node $node): array
{
return ['character' => '='];
}
}
```
51 changes: 51 additions & 0 deletions docs/2.0/xml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
layout: default
title: XML Rendering
description: Rendering Markdown documents in XML
redirect_from: /xml/
---

# XML Rendering

Version 2.0 introduced the ability to render Markdown `Document` objects in XML. This is particularly useful for debugging [custom extensions](/2.0/customization/).

To convert Markdown to XML, you would instantiate an [`Environment`](/2.0/customization/environment/), parse the Markdown into an [AST](/2.0/customization/abstract-syntax-tree/), and render it using the new `XmlRenderer`:

```php
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Parser\MarkdownParser;
use League\CommonMark\Xml\XmlRenderer;

$environment = new Environment();
$environment->addExtension(new CommonMarkCoreExtension());

$parser = new MarkdownParser($environment);
$renderer = new XmlRenderer($environment);

$document = $parser->parse('# **Hello** World!');

echo $renderer->renderDocument($document);
```

This will display XML output like this:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://commonmark.org/xml/1.0">
<heading level="1">
<strong>
<text>Hello</text>
</strong>
<text> World!</text>
</heading>
</document>
```

## Return Value

Like with `CommonMarkConverter::convertToHtml()`, the `renderDocument()` actually returns an instance of `League\CommonMark\Output\RenderedContentInterface`. You can cast this (implicitly, as shown above, or explicitly) to a `string` or call `getContent()` to get the final XML output.

## Customizing the XML Output

See the [rendering documentation](/2.0/customization/rendering/#xml-rendering) for information on customizing the XML output.
20 changes: 19 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/BlockQuoteRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class BlockQuoteRenderer implements NodeRendererInterface
final class BlockQuoteRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param BlockQuote $node
Expand All @@ -49,4 +50,21 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)
$innerSeparator . $filling . $innerSeparator
);
}

public function getXmlTagName(Node $node): string
{
return 'block_quote';
}

/**
* @param BlockQuote $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
26 changes: 25 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/FencedCodeRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\Xml;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class FencedCodeRenderer implements NodeRendererInterface
final class FencedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param FencedCode $node
Expand All @@ -49,4 +50,27 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)
new HtmlElement('code', $attrs->export(), Xml::escape($node->getLiteral()))
);
}

public function getXmlTagName(Node $node): string
{
return 'code_block';
}

/**
* @param FencedCode $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
FencedCode::assertInstanceOf($node);

if (($info = $node->getInfo()) === null || $info === '') {
return [];
}

return ['info' => $info];
}
}
22 changes: 21 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/HeadingRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class HeadingRenderer implements NodeRendererInterface
final class HeadingRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param Heading $node
Expand All @@ -41,4 +42,23 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)

return new HtmlElement($tag, $attrs, $childRenderer->renderNodes($node->children()));
}

public function getXmlTagName(Node $node): string
{
return 'heading';
}

/**
* @param Heading $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
Heading::assertInstanceOf($node);

return ['level' => $node->getLevel()];
}
}
16 changes: 15 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/HtmlBlockRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlFilter;
use League\CommonMark\Xml\XmlNodeRendererInterface;
use League\Config\ConfigurationAwareInterface;
use League\Config\ConfigurationInterface;

final class HtmlBlockRenderer implements NodeRendererInterface, ConfigurationAwareInterface
final class HtmlBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface, ConfigurationAwareInterface
{
/**
* @var ConfigurationInterface
Expand Down Expand Up @@ -53,4 +54,17 @@ public function setConfiguration(ConfigurationInterface $configuration): void
{
$this->config = $configuration;
}

public function getXmlTagName(Node $node): string
{
return 'html_block';
}

/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
16 changes: 15 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/IndentedCodeRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Util\Xml;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class IndentedCodeRenderer implements NodeRendererInterface
final class IndentedCodeRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param IndentedCode $node
Expand All @@ -44,4 +45,17 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)
new HtmlElement('code', $attrs, Xml::escape($node->getLiteral()))
);
}

public function getXmlTagName(Node $node): string
{
return 'code_block';
}

/**
* @return array<string, scalar>
*/
public function getXmlAttributes(Node $node): array
{
return [];
}
}
36 changes: 35 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/ListBlockRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class ListBlockRenderer implements NodeRendererInterface
final class ListBlockRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param ListBlock $node
Expand All @@ -49,4 +50,37 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)

return new HtmlElement($tag, $attrs, $innerSeparator . $childRenderer->renderNodes($node->children()) . $innerSeparator);
}

public function getXmlTagName(Node $node): string
{
return 'list';
}

/**
* @param ListBlock $node
*
* @return array<string, scalar>
*
* @psalm-suppress MoreSpecificImplementedParamType
*/
public function getXmlAttributes(Node $node): array
{
ListBlock::assertInstanceOf($node);

$data = $node->getListData();

if ($data->type === ListBlock::TYPE_BULLET) {
return [
'type' => $data->type,
'tight' => $node->isTight() ? 'true' : 'false',
];
}

return [
'type' => $data->type,
'start' => $data->start ?? 1,
'tight' => $node->isTight(),
'delimiter' => $data->delimiter ?? ListBlock::DELIM_PERIOD,
];
}
}
16 changes: 15 additions & 1 deletion src/Extension/CommonMark/Renderer/Block/ListItemRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@
use League\CommonMark\Renderer\ChildNodeRendererInterface;
use League\CommonMark\Renderer\NodeRendererInterface;
use League\CommonMark\Util\HtmlElement;
use League\CommonMark\Xml\XmlNodeRendererInterface;

final class ListItemRenderer implements NodeRendererInterface
final class ListItemRenderer implements NodeRendererInterface, XmlNodeRendererInterface
{
/**
* @param ListItem $node
Expand All @@ -51,6 +52,19 @@ public function render(Node $node, ChildNodeRendererInterface $childRenderer)
return new HtmlElement('li', $attrs, $contents);
}

public function getXmlTagName(Node $node): string
{
return 'item';
}

/**
* {@inheritDoc}
*/
public function getXmlAttributes(Node $node): array
{
return [];
}

private function startsTaskListItem(ListItem $block): bool
{
$firstChild = $block->firstChild();
Expand Down
Loading

0 comments on commit 4b9bf21

Please sign in to comment.