dealnews/datocms-structured-text-to-html-string

Convert DatoCMS Structured Text field to HTML string

Installs: 30

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/dealnews/datocms-structured-text-to-html-string

0.1.1 2026-02-23 22:21 UTC

This package is auto-updated.

Last update: 2026-02-23 22:32:36 UTC


README

Convert DatoCMS Structured Text (DAST format) to HTML strings. PHP port of the official JavaScript library.

Requirements

  • PHP 8.2+
  • Composer

Installation

composer require dealnews/datocms-structured-text-to-html-string

Basic Usage

<?php
require_once 'vendor/autoload.php';

use DealNews\StructuredText\Renderer;

// Create renderer instance
$renderer = new Renderer();

// Simple structured text
$structured_text = [
    'schema'   => 'dast',
    'document' => [
        'type'     => 'root',
        'children' => [
            [
                'type'     => 'paragraph',
                'children' => [
                    [
                        'type'  => 'span',
                        'value' => 'Hello world!',
                    ],
                ],
            ],
        ],
    ],
];

$html = $renderer->render($structured_text);
// Output: <p>Hello world!</p>

Features

  • ✅ Converts DAST documents to HTML strings
  • ✅ Supports custom node and mark rendering rules
  • ✅ Handles blocks, inline blocks, inline items, and item links
  • ✅ Text transformation support
  • ✅ Newline handling (converts to <br> tags)
  • ✅ Type-safe with full PHPDoc coverage

Advanced Usage

Custom Text Transformation

use DealNews\StructuredText\Renderer;
use DealNews\StructuredText\RenderSettings;

$renderer = new Renderer();

$settings = new RenderSettings();
$settings->render_text = function(string $text): string {
    return str_replace('Hello', 'Howdy', $text);
};

$html = $renderer->render($structured_text, $settings);

Custom Node Rules

Change how specific node types are rendered:

use DealNews\StructuredText\Renderer;
use DealNews\StructuredText\RenderRule;
use DealNews\StructuredText\RenderSettings;
use DealNews\StructuredText\Utils;

$settings = new RenderSettings();
$settings->custom_node_rules = [
    RenderRule::forNode(
        function($node) {
            return Utils::isHeading($node);
        },
        function($context) {
            // Increase heading level by 1
            $level = $context['node']['level'] + 1;
            return $context['adapter']->renderNode(
                "h{$level}",
                ['key' => $context['key']],
                $context['children']
            );
        }
    ),
];

$html = Renderer::render($structured_text, $settings);

Custom Mark Rules

Change how text marks (bold, italic, etc.) are rendered:

use DealNews\StructuredText\RenderRule;

$settings->custom_mark_rules = [
    $rule_builder->forMark('strong', function($context) {
        // Render strong as <b> instead of <strong>
        return $context['adapter']->renderNode(
            'b',
            ['key' => $context['key']],
            $context['children']
        );
    }),
];

Rendering Blocks and Links

$graphql_response = [
    'value'  => [
        'schema'   => 'dast',
        'document' => [
            'type'     => 'root',
            'children' => [
                [
                    'type'     => 'paragraph',
                    'children' => [
                        [
                            'type'  => 'span',
                            'value' => 'Check out ',
                        ],
                        [
                            'type'     => 'itemLink',
                            'item'     => '123',
                            'children' => [
                                [
                                    'type'  => 'span',
                                    'value' => 'this article',
                                ],
                            ],
                        ],
                    ],
                ],
                [
                    'type' => 'block',
                    'item' => '456',
                ],
            ],
        ],
    ],
    'blocks' => [
        (object) [
            'id'    => '456',
            '__typename' => 'ImageRecord',
            'url'   => 'https://example.com/image.jpg',
            'alt'   => 'Example image',
        ],
    ],
    'links'  => [
        (object) [
            'id'    => '123',
            '__typename' => 'ArticleRecord',
            'title' => 'How to Code',
            'slug'  => 'how-to-code',
        ],
    ],
];

$settings = new RenderSettings();

// Render inline item references
$settings->render_inline_record = function($context) {
    $record = $context['record'];
    $adapter = $context['adapter'];
    
    if ($record->__typename === 'ArticleRecord') {
        return $adapter->renderNode(
            'a',
            ['href' => "/articles/{$record->slug}"],
            $record->title
        );
    }
    
    return null;
};

// Render item links
$settings->render_link_to_record = function($context) {
    $record = $context['record'];
    $adapter = $context['adapter'];
    $children = $context['children'];
    
    if ($record->__typename === 'ArticleRecord') {
        return $adapter->renderNode(
            'a',
            ['href' => "/articles/{$record->slug}"],
            $children
        );
    }
    
    return null;
};

// Render blocks
$settings->render_block = function($context) {
    $record = $context['record'];
    $adapter = $context['adapter'];
    
    if ($record->__typename === 'ImageRecord') {
        return $adapter->renderNode(
            'img',
            [
                'src' => $record->url,
                'alt' => $record->alt,
            ]
        );
    }
    
    return null;
};

$html = Renderer::render($graphql_response, $settings);
// Output: <p>Check out <a href="/articles/how-to-code">this article</a></p>
//         <img src="https://example.com/image.jpg" alt="Example image" />

API Reference

Renderer::render($structured_text_or_node, ?RenderSettings $settings = null): ?string

Main rendering function. Converts structured text to HTML.

Parameters:

  • $structured_text_or_node - Structured text from DatoCMS or a document node
  • $settings - Optional rendering configuration

Returns: HTML string or null if input is null

Throws: RenderError if rendering fails

RenderSettings

Configuration object for customizing rendering behavior.

Properties:

  • custom_node_rules - Array of custom node rendering rules
  • custom_mark_rules - Array of custom mark rendering rules
  • render_inline_record - Callback for rendering inline items
  • render_link_to_record - Callback for rendering item links
  • render_block - Callback for rendering blocks
  • render_inline_block - Callback for rendering inline blocks
  • render_text - Callback for transforming text
  • render_node - Custom node renderer (advanced)
  • render_fragment - Custom fragment renderer (advanced)
  • meta_transformer - Function to transform link meta into attributes

Utility Functions

use DealNews\StructuredText\Utils;

Utils::isBlock($node);        // Check if node is a block
Utils::isInlineBlock($node);  // Check if node is inline block
Utils::isInlineItem($node);   // Check if node is inline item
Utils::isItemLink($node);     // Check if node is item link
Utils::isHeading($node);      // Check if node is heading
Utils::isParagraph($node);    // Check if node is paragraph
Utils::isSpan($node);         // Check if node is span
Utils::isLink($node);         // Check if node is link

Rule Builders

use DealNews\StructuredText\RenderRule;

// Create a custom node rule
$rule = RenderRule::forNode(
    function($node) { return $node['type'] === 'heading'; },
    function($context) { /* return HTML */ }
);

// Create a custom mark rule
$mark_rule = $rule_builder->forMark('strong', function($context) {
    /* return HTML */
});

Error Handling

The library throws RenderError exceptions when:

  • Required render callbacks are missing (e.g., render_block when blocks are present)
  • Referenced records cannot be found in the links/blocks arrays
  • Document structure is invalid
use DealNews\StructuredText\RenderError;
use DealNews\StructuredText\Renderer;

try {
    $html = Renderer::render($structured_text, $settings);
} catch (RenderError $e) {
    echo "Rendering failed: " . $e->getMessage();
    $problematic_node = $e->getNode();
    // Handle error...
}

Default Mark Rendering

Mark HTML Tag
strong <strong>
code <code>
emphasis <em>
underline <u>
strikethrough <s>
highlight <mark>

Edge Cases

  • Newlines in text: Converted to <br /> tags automatically
  • Empty nodes: Rendered as self-closing tags (e.g., <hr />)
  • Null renderers: Returning null from custom renderers skips that node
  • Missing records: Throws RenderError to prevent silent failures

Development

Requirements

  • PHP 8.2+
  • Composer
  • PHPUnit 11.5+

Running Tests

composer install
./vendor/bin/phpunit

Running Examples

php examples/basic.php
php examples/custom_rendering.php
php examples/blocks_and_links.php

Testing

The library includes mocks-friendly dependency injection:

// Inject a mock RenderRule for testing
$mock_rule_builder = $this->createMock(RenderRule::class);
$renderer = new Renderer($mock_rule_builder);

$html = $renderer->render($structured_text, $settings);

License

BSD 3-Clause License - see LICENSE file for details

Credits

This is a PHP port of the official DatoCMS Structured Text to HTML String JavaScript library.

Ported and maintained by DealNews.