PHPackages                             automattic/block-delimiter - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. automattic/block-delimiter

ActiveJetpack-library[Utility &amp; Helpers](/categories/utility)

automattic/block-delimiter
==========================

Efficiently work with block structure

v0.3.5(3mo ago)0538.5k—7.6%4GPL-2.0-or-laterPHPPHP &gt;=7.2CI failing

Since May 28Pushed 1mo agoCompare

[ Source](https://github.com/Automattic/block-delimiter)[ Packagist](https://packagist.org/packages/automattic/block-delimiter)[ RSS](/packages/automattic-block-delimiter/feed)WikiDiscussions trunk Synced 1mo ago

READMEChangelogDependencies (4)Versions (11)Used By (4)

Block Delimiter
===============

[](#block-delimiter)

Efficiently work with block structure

How to install block-delimiter
------------------------------

[](#how-to-install-block-delimiter)

To use this package in your WordPress plugin, you can require both this package and the [Jetpack Autoloader](https://packagist.org/packages/automattic/jetpack-autoloader) in your project's `composer.json` file.

Overview
--------

[](#overview)

The Block Delimiter package provides an efficient, streaming parser for working with WordPress block structure without the memory overhead of `parse_blocks()`. It's designed for scenarios where you need to inspect, find, or modify specific blocks without parsing the entire block tree.

The package includes two classes:

- **`Block_Scanner`** (recommended): A high-performance, mutable scanner that provides the best CPU performance while maintaining near-zero memory overhead.
- **`Block_Delimiter`** (legacy): A generator-based approach that returns new instances for each delimiter, suitable for existing code that relies on the `scan_delimiters()` interface.

Block\_Scanner (Recommended)
----------------------------

[](#block_scanner-recommended)

The `Block_Scanner` class is modeled after the WordPress HTML API and provides the best performance characteristics. It mutates itself during scanning and requires a new instance for each document scan.

### Basic Block Scanning

[](#basic-block-scanning)

Find and iterate through all block delimiters in a document:

```
use Automattic\Block_Scanner;

$post_content = '
Hello world!

';

$scanner = Block_Scanner::create( $post_content );

while ( $scanner->next_delimiter() ) {
    echo "Found block: " . $scanner->get_block_type() . "\n";
    echo "Type: " . $scanner->get_delimiter_type() . "\n";
}
```

**Output:**

```
Found block: core/paragraph
Type: opener
Found block: core/paragraph
Type: closer
Found block: core/image
Type: opener
Found block: core/image
Type: closer

```

### Finding Specific Block Types

[](#finding-specific-block-types)

Efficiently find blocks of a specific type without parsing everything:

```
use Automattic\Block_Scanner;

$post_content = '
Welcome to my blog!

...
';

$scanner = Block_Scanner::create( $post_content );

// Find the first image block
while ( $scanner->next_delimiter() ) {
    if ( ! $scanner->opens_block( 'image' ) ) {
        continue;
    }

    $attributes = $scanner->allocate_and_return_parsed_attributes();
    if ( isset( $attributes['id'] ) ) {
        echo "Found image with ID: " . $attributes['id'];
        break;
    }
}
```

**Output:**

```
Found image with ID: 456

```

### Extracting Block Attributes

[](#extracting-block-attributes)

Parse JSON attributes only when needed:

```
use Automattic\Block_Scanner;

$post_content = '
This paragraph has a large font size.

This paragraph has no custom font size.

This paragraph has a small font size and primary color.
';

$scanner = Block_Scanner::create( $post_content );

while ( $scanner->next_delimiter() ) {
    if ( $scanner->opens_block( 'paragraph' ) ) {
        $attributes = $scanner->allocate_and_return_parsed_attributes();
        if ( isset( $attributes['fontSize'] ) ) {
            echo "Paragraph with font size: " . $attributes['fontSize'] . "\n";
        }
    }
}
```

**Output:**

```
Paragraph with font size: large
Paragraph with font size: small

```

### Counting Block Types

[](#counting-block-types)

Get a count of all block types in a document:

```
use Automattic\Block_Scanner;

$post_content = '
My Blog Post

Introduction paragraph.

Another paragraph.

Item 1Item 2
';

function count_block_types_in( string $html ): array {
    $block_counts = [];
    $scanner = Block_Scanner::create( $html );

    while ( $scanner->next_delimiter() ) {
        if ( $scanner->opens_block() ) {
            $block_type = $scanner->get_block_type();
            $block_counts[ $block_type ] = ( $block_counts[ $block_type ] ?? 0 ) + 1;
        }
    }

    ksort( $block_counts );
    return $block_counts;
}

$block_counts = count_block_types_in( $post_content );
print_r( $block_counts );
```

**Output:**

```
Array
(
    [core/heading] => 1
    [core/image] => 1
    [core/list] => 1
    [core/paragraph] => 2
)

```

### Error Handling

[](#error-handling)

Check for parsing errors:

```
use Automattic\Block_Scanner;

$post_content = '
This block has invalid JSON attributes.

';

$scanner = Block_Scanner::create( $post_content );

while ( $scanner->next_delimiter() ) {
    if ( $scanner->opens_block() ) {
        $attributes = $scanner->allocate_and_return_parsed_attributes();
        if ( null === $attributes && $scanner->get_last_json_error() !== JSON_ERROR_NONE ) {
            echo "Invalid JSON in " . $scanner->get_block_type() . " block\n";
        } elseif ( is_array( $attributes ) ) {
            echo "Valid " . $scanner->get_block_type() . " block\n";
        } else {
            echo "No attributes in " . $scanner->get_block_type() . " block\n";
        }
    }
}

// Check for incomplete input
$incomplete_content = '
Hello world!

';

foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $where => $delimiter ) {
    // $where is an array: [ byte_offset, byte_length ]
    // $delimiter is a Block_Delimiter instance

    echo "Found block: " . $delimiter->allocate_and_return_block_type() . "\n";
    echo "Type: " . $delimiter->get_delimiter_type() . "\n";
}
```

**Output:**

```
Found block: core/paragraph
Type: opener
Found block: core/paragraph
Type: closer
Found block: core/image
Type: opener
Found block: core/image
Type: closer

```

### Finding Specific Block Types

[](#finding-specific-block-types-1)

Efficiently find blocks of a specific type without parsing everything:

```
use Automattic\Block_Delimiter;

$post_content = '
Welcome to my blog!

...
';

// Find the first image block
foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $delimiter ) {
    if ( ! $delimiter->is_block_type( 'image' ) ) {
        continue;
    }

    if ( Block_Delimiter::OPENER === $delimiter->get_delimiter_type() ) {
        $attributes = $delimiter->allocate_and_return_parsed_attributes();
        if ( isset( $attributes['id'] ) ) {
            echo "Found image with ID: " . $attributes['id'];
            break;
        }
    }
}
```

**Output:**

```
Found image with ID: 456

```

### Extracting Block Attributes

[](#extracting-block-attributes-1)

Parse JSON attributes only when needed:

```
use Automattic\Block_Delimiter;

$post_content = '
This paragraph has a large font size.

This paragraph has no custom font size.

This paragraph has a small font size and primary color.
';

foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $delimiter ) {
    if ( $delimiter->is_block_type( 'core/paragraph' ) &&
         Block_Delimiter::OPENER === $delimiter->get_delimiter_type() ) {

        $attributes = $delimiter->allocate_and_return_parsed_attributes();
        if ( isset( $attributes['fontSize'] ) ) {
            echo "Paragraph with font size: " . $attributes['fontSize'] . "\n";
        }
    }
}
```

**Output:**

```
Paragraph with font size: large
Paragraph with font size: small

```

### Counting Block Types

[](#counting-block-types-1)

Get a count of all block types in a document:

```
use Automattic\Block_Delimiter;

$post_content = '
My Blog Post

Introduction paragraph.

Another paragraph.

Item 1Item 2
';

function count_block_types_in( string $html ): array {
    $block_counts = [];

    foreach ( Block_Delimiter::scan_delimiters( $html ) as $delimiter ) {
        if ( Block_Delimiter::OPENER === $delimiter->get_delimiter_type() ) {
            $block_type = $delimiter->allocate_and_return_block_type();
            $block_counts[ $block_type ] = ( $block_counts[ $block_type ] ?? 0 ) + 1;
        }
    }

    ksort( $block_counts );
    return $block_counts;
}

$block_counts = count_block_types_in( $post_content );
print_r( $block_counts );
```

**Output:**

```
Array
(
    [core/heading] => 1
    [core/image] => 1
    [core/list] => 1
    [core/paragraph] => 2
)

```

### Extracting Complete Block Content

[](#extracting-complete-block-content)

Extract an entire block including its delimiters and content:

```
use Automattic\Block_Delimiter;

$post_content = '
First paragraph.

Section Title

Second paragraph with more content.
';

function extract_block( string $block_name, string $html ): ?string {
    $depth = 0;
    $starts_at = null;

    foreach ( Block_Delimiter::scan_delimiters( $html ) as $where => $delimiter ) {
        if ( ! $delimiter->is_block_type( $block_name ) ) {
            continue;
        }

        switch ( $delimiter->get_delimiter_type() ) {
            case Block_Delimiter::VOID:
                return substr( $html, $where[0], $where[1] );

            case Block_Delimiter::OPENER:
                $depth++;
                $starts_at = $starts_at ?? $where[0];
                break;

            case Block_Delimiter::CLOSER:
                if ( --$depth === 0 ) {
                    return substr( $html, $starts_at, $where[0] + $where[1] - $starts_at );
                }
        }
    }

    return null;
}

$heading_block = extract_block( 'heading', $post_content );
echo $heading_block;
```

**Output:**

```

Section Title

```

### Modifying Block Content

[](#modifying-block-content)

Transform block content efficiently without parsing the entire tree:

```
use Automattic\Block_Delimiter;

$post_content = '
Some text content.

More text content.

';

function add_css_class_to_images( string $post_content, string $css_class ): string {
    $output = '';
    $starts_at = null;
    $was_at = 0;

    foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $where => $delimiter ) {
        if ( ! $delimiter->is_block_type( 'image' ) ) {
            continue;
        }

        list( $at, $length ) = $where;

        if ( Block_Delimiter::OPENER === $delimiter->get_delimiter_type() ) {
            $starts_at = $at + $length;
        } elseif ( Block_Delimiter::CLOSER === $delimiter->get_delimiter_type() ) {
            // Copy untouched content before this block
            $output .= substr( $post_content, $was_at, $starts_at - $was_at );

            // Transform the block content
            $block_content = substr( $post_content, $starts_at, $at - $starts_at );
            $output .= add_css_class( $block_content, $css_class );

            $was_at = $at;
        }
    }

    // Add any remaining content
    $output .= substr( $post_content, $was_at );
    return $output;
}

function add_css_class( string $html, string $css_class ): string {
    // Simple example - add class to figure elements
    return str_replace( 'class="wp-block-image"', 'class="wp-block-image ' . $css_class . '"', $html );
}

$modified_content = add_css_class_to_images( $post_content, 'custom-image-style' );
echo $modified_content;
```

**Output:**

```

Some text content.

More text content.

```

### Error Handling

[](#error-handling-1)

Check for parsing errors:

```
use Automattic\Block_Delimiter;

$post_content = '
This block has invalid JSON attributes.

';

foreach ( Block_Delimiter::scan_delimiters( $post_content ) as $delimiter ) {
    if ( Block_Delimiter::OPENER === $delimiter->get_delimiter_type() ) {
        $attributes = $delimiter->allocate_and_return_parsed_attributes();
        if ( null === $attributes && $delimiter->get_last_json_error() !== JSON_ERROR_NONE ) {
            echo "Invalid JSON in " . $delimiter->allocate_and_return_block_type() . " block\n";
        } elseif ( is_array( $attributes ) ) {
            echo "Valid " . $delimiter->allocate_and_return_block_type() . " block\n";
        } else {
            echo "No attributes in " . $delimiter->allocate_and_return_block_type() . " block\n";
        }
    }
}

// Check for incomplete input
$incomplete_content = '
