Skip to content

Stream CSS through the existing token and URL APIs - #314

Merged
adamziel merged 44 commits into
trunkfrom
codex/stream-resumable-css
Sep 10, 2026
Merged

adamziel merged 44 commits into
trunkfrom
codex/stream-resumable-css

Conversation

@adamziel

@adamziel adamziel commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

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:

$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:

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 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.

@adamziel adamziel changed the title Stream and resume CSS URL rewriting Rewrite URL mappings in streamed CSS file chunks Sep 8, 2026
@adamziel
adamziel changed the base branch from trunk to codex/css-token-stream September 8, 2026 08:48
adamziel added a commit that referenced this pull request Sep 9, 2026
Lets a CSS token span input chunks and continue in a new PHP process
without restarting the file.

Review order: #315 (merged) → #316#317#314.

The processor keeps unfinished input and parses it again after another
read. Whole strings and streamed input use the same `next_token()`,
getters, and value setter. `flush_processed_css()` returns edited,
completed input and releases its source bytes. The cursor contains the
remaining bytes and whether more input is expected; the caller saves it
with the source and output offsets after writing the output.

This is layer 3 of the CSS stack, based on #316. It adds input,
flushing, and the token cursor together. It contains no URL mappings or
`rewrite_chunk()`; those remain in #314. ASCII spans are scanned
together so reparsing long identifiers and URLs does not walk every byte
in PHP.

A large token increases memory use, cursor size, and repeated parsing
work. Token size is not capped. The README spells out that cost and
links the real-file caller.

## Testing

The streamed tokens match the whole-string corpus, including one-byte
reads, escapes, and invalid UTF-8. Separate PHP processes edit a
multi-chunk file, stop before or after a saved checkpoint, and resume
with the unfinished token. The written file must match whole-string
edits without missing or duplicated bytes. The full DataLiberation run
completed with 2,618 tests and 16 skips. PHPCS passes.
Base automatically changed from codex/css-token-stream to trunk September 9, 2026 18:23
@adamziel adamziel changed the title Rewrite URL mappings in streamed CSS file chunks Stream CSS through the existing token and URL APIs Sep 10, 2026
@adamziel
adamziel merged commit 160ae2e into trunk Sep 10, 2026
29 checks passed
@adamziel
adamziel deleted the codex/stream-resumable-css branch September 10, 2026 09:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant