Skip to content

v0.10.0

@adamziel adamziel tagged this 10 Sep 09:44
Lets CSS callers append source bytes while using the same scan, read,
and edit methods as whole-string callers. Streaming now follows XML's
input and resume model. The whole-string public API stays unchanged.

## Split input

A read can end inside `url(https://old.exa`. The processor waits for the
rest of the token. Once it arrives, the caller uses the ordinary
`next_url()`, `get_raw_url()`, and `set_raw_url()` methods:

```php
$processor = CSSURLProcessor::create_for_streaming();
$parts = array( 'a{src:url(https://old.exa', 'mple/photo.png)}' );
foreach ( $parts as $index => $bytes ) {
    $processor->append_bytes( $bytes );
    if ( $index === count( $parts ) - 1 ) {
        $processor->input_finished();
    }
    while ( $processor->next_url() ) {
        if ( 'https://old.example/photo.png' === $processor->get_raw_url() ) {
            $processor->set_raw_url( 'https://new.example/photo.png' );
        }
    }
    echo $processor->flush_processed_css();
}
```

Output:

```css
a{src:url("https://new.example/photo.png")}
```

`CSSProcessor` uses the same input methods with `next_token()` and its
existing token getters and setters. A false scan result can mean more
bytes are needed; `is_paused_at_incomplete_input()` reports that state.
Call `input_finished()` only at the actual source end, then scan the
remaining tokens. `is_finished()` reports completion.

There is no separate `rewrite_chunk()` API and no mapping table in the
processor. The caller chooses replacements. Existing setters still quote
and escape complete values, so replacing `old.png` with `new.png`
changes `url(old.png)` to `url("new.png")`.

## Output and resume

Appending bytes does not require flushing first. `get_updated_css()`
keeps working on the retained input. `flush_processed_css()` returns
completed output with edits applied, clears the current token, and
releases those source bytes. The caller can flush after each edited URL
to avoid retaining many large replacements.

For example, after flushing `a{src:`, a cursor saved inside the
unfinished URL points back to `url(`. Save `get_reentrancy_cursor()`
with `get_token_byte_offset_in_the_input_stream()`. Resume reads source
bytes from that offset. The opaque cursor stores parser context, not
unfinished CSS or edits. A cursor saved on a current token reads that
token again, as XML does.

For file rewrites, write and flush output before saving the cursor and
both file offsets together. Use the processor's source offset, not the
input handle's position. On resume, truncate output to the saved output
offset and reread source from the saved source offset. The [file
caller](https://github.com/WordPress/php-toolkit/blob/codex/stream-resumable-css/components/DataLiberation/Tests/fixtures/css-stream/rewrite-file.php)
shows the complete flow. Keep source bytes and edit rules unchanged
between runs; the processor does not check them.

An unfinished token still has no size cap and is parsed again after each
read. A large comment or URL can therefore use a lot of memory and
parsing time. The cursor no longer copies that token. More than 128
nested `image-set()` calls still throws.

## Tests

Tests compare whole-string and streamed results at every byte split,
restore at each byte, retain edits across appends, replay current URL
strings with their surrounding context, reject appends after EOF, and
reject corrupt cursors. Large-token and output-expansion cases check
buffer release and caller-controlled flushing.

Separate PHP processes rewrite real files, stop before or after saving
progress, then resume. Another case stops after marking EOF but before
editing the last URL. Tests check exact output, unchanged source, saved
offsets, NUL preprocessing, and the nesting-limit error on both the
initial run and resume. The README example also runs in the docs-snippet
checks.

Reprint's WordPress/reprint#764 must use this input API and a package
release containing it before it can ship.
Assets 2
Loading