r the processor is matched on an HTML span.
*/
public function is_html(): bool {
return self::HTML_SPAN === $this->state;
}
/**
* Indicates if the matched delimiter is an HTML span and comprises more
* than whitespace characters, i.e. contains real content.
*
* Many block serializers introduce newlines between block delimiters,
* so the presence of top-level non-block content does not imply that
* there are “real” freeform HTML blocks. Checking if there is content
* beyond whitespace is a more certain check, such as for determining
* whether to load CSS for the freeform or fallback block type.
*
* @since 6.9.0
*
* @see self::is_html()
*
* @return bool Whether the currently-matched delimiter is an HTML
* span containing non-whitespace text.
*/
public function is_non_whitespace_html(): bool {
if ( ! $this->is_html() ) {
return false;
}
$length = $this->matched_delimiter_at - $this->after_previous_delimiter;
$whitespace_length = strspn(
$this->source_text,
" \t\f\r\n",
$this->after_previous_delimiter,
$length
);
return $whitespace_length !== $length;
}
/**
* Returns the string content of a matched HTML span, or `null` otherwise.
*
* @since 6.9.0
*
* @return string|null Raw HTML content, or `null` if not currently matched on HTML.
*/
public function get_html_content(): ?string {
if ( ! $this->is_html() ) {
return null;
}
return substr(
$this->source_text,
$this->after_previous_delimiter,
$this->matched_delimiter_at - $this->after_previous_delimiter
);
}
/**
* Allocates a substring for the block type and returns the fully-qualified
* name, including the namespace, if matched on a delimiter, otherwise `null`.
*
* This function is like {@see self::get_printable_block_type()} but when
* paused on a freeform HTML block, will return `null` instead of “core/freeform”.
* The `null` behavior matches what {@see \parse_blocks()} returns but may not
* be as useful as having a string value.
*
* This function allocates a substring for the given block type. This
* allocation will be small and likely fine in most cases, but it's
* preferable to call {@see self::is_block_type()} if only needing
* to know whether the delimiter is for a given block type, as that
* function is more efficient for this purpose and avoids the allocation.
*
* Example:
*
* // Avoid.
* 'core/paragraph' = $processor->get_block_type();
*
* // Prefer.
* $processor->is_block_type( 'core/paragraph' );
* $processor->is_block_type( 'paragraph' );
* $processor->is_block_type( 'core/freeform' );
*
* // Freeform HTML content has no block type.
* $processor = new WP_Block_Processor( 'non-block content' );
* $processor->next_token();
* null === $processor->get_block_type();
*
* @since 6.9.0
*
* @see self::are_equal_block_types()
*
* @return string|null Fully-qualified block namespace and type, e.g. "core/paragraph",
* if matched on an explicit delimiter, otherwise `null`.
*/
public function get_block_type(): ?string {
if (
self::READY === $this->state ||
self::COMPLETE === $this->state ||
self::INCOMPLETE_INPUT === $this->state
) {
return null;
}
// This is a core/freeform text block, it’s special.
if ( $this->is_html() ) {
return null;
}
$block_type = substr( $this->source_text, $this->namespace_at, $this->name_at - $this->namespace_at + $this->name_length );
return self::normalize_block_type( $block_type );
}
/**
* Allocates a printable substring for the block type and returns the fully-qualified
* name, including the namespace, if matched on a delimiter or freeform block, otherwise `null`.
*
* This function is like {@see self::get_block_type()} but when paused on a freeform
* HTML block, will return “core/freeform” instead of `null`. The `null` behavior matches
* what {@see \parse_blocks()} returns but may not be as useful as having a string value.
*
* This function allocates a substring for the given block type. This
* allocation will be small and likely fine in most cases, but it's
* preferable to call {@see self::is_block_type()} if only needing
* to know whether the delimiter is for a given block type, as that
* function is more efficient for this purpose and avoids the allocation.
*
* Example:
*
* // Avoid.
* 'core/paragraph' = $processor->get_printable_block_type();
*
* // Prefer.
* $processor->is_block_type( 'core/paragraph' );
* $processor->is_block_type( 'paragraph' );
* $processor->is_block_type( 'core/freeform' );
*
* // Freeform HTML content is given an implicit type.
* $processor = new WP_Block_Processor( 'non-block content' );
* $processor->next_token();
* 'core/freeform' === $processor->get_printable_block_type();
*
* @since 6.9.0
*
* @see self::are_equal_block_types()
*
* @return string|null Fully-qualified block namespace and type, e.g. "core/paragraph",
* if matched on an explicit delimiter or freeform block, otherwise `null`.
*/
public function get_printable_block_type(): ?string {
if (
self::READY === $this->state ||
self::COMPLETE === $this->state ||
self::INCOMPLETE_INPUT === $this->state
) {
return null;
}
// This is a core/freeform text block, it’s special.
if ( $this->is_html() ) {
return 1 === count( $this->open_blocks_at )
? 'core/freeform'
: '#innerHTML';
}
$block_type = substr( $this->source_text, $this->namespace_at, $this->name_at - $this->namespace_at + $this->name_length );
return self::normalize_block_type( $block_type );
}
/**
* Normalizes a block name to ensure that missing implicit “core” namespaces are present.
*
* Example:
*
* 'core/paragraph' === WP_Block_Processor::normalize_block_byte( 'paragraph' );
* 'core/paragraph' === WP_Block_Processor::normalize_block_byte( 'core/paragraph' );
* 'my/paragraph' === WP_Block_Processor::normalize_block_byte( 'my/paragraph' );
*
* @since 6.9.0
*
* @param string $block_type Valid block name, potentially without a namespace.
* @return string Fully-qualified block type including namespace.
*/
public static function normalize_block_type( string $block_type ): string {
return str_contains( $block_type, '/' )
? $block_type
: "core/{$block_type}";
}
/**
* Returns a lazy wrapper around the block attributes, which can be used
* for efficiently interacting with the JSON attributes.
*
* This stub hints that there should be a lazy interface for parsing
* block attributes but doesn’t define it. It serves both as a placeholder
* for one to come as well as a guard against implementing an eager
* function in its place.
*
* @throws Exception This function is a stub for subclasses to implement
* when providing streaming attribute parsing.
*
* @since 6.9.0
*
* @see self::allocate_and_return_parsed_attributes()
*
* @return never
*/
public function get_attributes() {
throw new Exception( 'Lazy attribute parsing not yet supported' );
}
/**
* Attempts to parse and return the entire JSON attributes from the delimiter,
* allocating memory and processing the JSON span in the process.
*
* This does not return any parsed attributes for a closing block delimiter
* even if there is a span of JSON content; this JSON is a parsing error.
*
* Consider calling {@see static::get_attributes()} instead if it's not
* necessary to read all the attributes at the same time, as that provides
* a more efficient mechanism for typical use cases.
*
* Since the JSON span inside the comment delimiter may not be valid JSON,
* this function will return `null` if it cannot parse the span and set the
* {@see static::get_last_json_error()} to the appropriate JSON_ERROR_ constant.
*
* If the delimiter contains no JSON span, it will also return `null`,
* but the last error will be set to {@see \JSON_ERROR_NONE}.
*
* Example:
*
* $processor = new WP_Block_Processor( '' );
* $processor->next_delimiter();
* $memory_hungry_and_slow_attributes = $processor->allocate_and_return_parsed_attributes();
* $memory_hungry_and_slow_attributes === array( 'url' => 'https://wordpress.org/favicon.ico' );
*
* $processor = new WP_Block_Processor( '' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
* JSON_ERROR_NONE = $processor->get_last_json_error();
*
* $processor = new WP_Block_Processor( '' );
* $processor->next_delimiter();
* array() === $processor->allocate_and_return_parsed_attributes();
*
* $processor = new WP_Block_Processor( '' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
*
* $processor = new WP_Block_Processor( '' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
* JSON_ERROR_CTRL_CHAR = $processor->get_last_json_error();
*
* @since 6.9.0
*
* @return array|null Parsed JSON attributes, if present and valid, otherwise `null`.
*/
public function allocate_and_return_parsed_attributes(): ?array {
$this->last_json_error = JSON_ERROR_NONE;
if ( self::CLOSER === $this->type || $this->is_html() || 0 === $this->json_length ) {
return null;
}
$json_span = substr( $this->source_text, $this->json_at, $this->json_length );
$parsed = json_decode( $json_span, null, 512, JSON_OBJECT_AS_ARRAY | JSON_INVALID_UTF8_SUBSTITUTE );
$last_error = json_last_error();
$this->last_json_error = $last_error;
return ( JSON_ERROR_NONE === $last_error && is_array( $parsed ) )
? $parsed
: null;
}
/**
* Returns the span representing the currently-matched delimiter, if matched, otherwise `null`.
*
* Example:
*
* $processor = new WP_Block_Processor( '' );
* null === $processor->get_span();
*
* $processor->next_delimiter();
* WP_HTML_Span( 0, 17 ) === $processor->get_span();
*
* @since 6.9.0
*
* @return WP_HTML_Span|null Span of text in source text spanning matched delimiter.
*/
public function get_span(): ?WP_HTML_Span {
switch ( $this->state ) {
case self::HTML_SPAN:
return new WP_HTML_Span( $this->after_previous_delimiter, $this->matched_delimiter_at - $this->after_previous_delimiter );
case self::MATCHED:
return new WP_HTML_Span( $this->matched_delimiter_at, $this->matched_delimiter_length );
default:
return null;
}
}
//
// Constant declarations that would otherwise pollute the top of the class.
//
/**
* Indicates that the block comment delimiter closes an open block.
*
* @see self::$type
*
* @since 6.9.0
*/
const CLOSER = 'closer';
/**
* Indicates that the block comment delimiter opens a block.
*
* @see self::$type
*
* @since 6.9.0
*/
const OPENER = 'opener';
/**
* Indicates that the block comment delimiter represents a void block
* with no inner content of any kind.
*
* @see self::$type
*
* @since 6.9.0
*/
const VOID = 'void';
/**
* Indicates that the processor is ready to start parsing but hasn’t yet begun.
*
* @see self::$state
*
* @since 6.9.0
*/
const READY = 'processor-ready';
/**
* Indicates that the processor is matched on an explicit block delimiter.
*
* @see self::$state
*
* @since 6.9.0
*/
const MATCHED = 'processor-matched';
/**
* Indicates that the processor is matched on the opening of an implicit freeform delimiter.
*
* @see self::$state
*
* @since 6.9.0
*/
const HTML_SPAN = 'processor-html-span';
/**
* Indicates that the parser started parsing a block comment delimiter, but
* the input document ended before it could finish. The document was likely truncated.
*
* @see self::$state
*
* @since 6.9.0
*/
const INCOMPLETE_INPUT = 'incomplete-input';
/**
* Indicates that the processor has finished parsing and has nothing left to scan.
*
* @see self::$state
*
* @since 6.9.0
*/
const COMPLETE = 'processor-complete';
}