API / Minn\Html

Minn\Html

The HTML tag processor. 7 classes, 74 public methods and 19 internals. This page is the public surface; each class name opens the whole class.

Decoder final class

public/minn/src/Minn/Html/Decoder.php · 129 lines · Open the class →

Character reference decoding for text and attribute values: numeric and named references, the legacy names that work without a semicolon (not in an attribute when an "=" or alphanumeric follows), unknown ones left as is.

statictext(string $raw): string

Text with its character references decoded.

staticattribute(string $raw): string

An attribute value with its character references decoded.

staticreference(string $context, string $text, int $at): ?array

The character reference at $at in a context ("data" or "attribute"): its text and how many bytes it spans, or null when none decodes there.

@return array{0: string, 1: int}|null
staticchar(int $code): string

The UTF-8 text for a code point, U+FFFD for one that cannot be a character, Windows-1252's for the C1 range.

insidedecode(), codePoint(), legacy()

Edits final class

public/minn/src/Minn/Html/Edits.php · 336 lines · Open the class →

The edits pending on the current token: attribute sets and removals, class additions and removals, and a text replacement, in call order. They answer reads before they are written, and become the source replacements the reader splices in when it moves on.

staticvalidName(string $name): bool

Whether an attribute name may be set at all, by the reference's rules.

methodsetAttribute(string $name, string|true $value): void

Records a set; setting class outright forgets the class edits before it.

methodsetEscapedAttribute(string $name, string $escaped): void

Records an attribute value that comes already escaped (a URL through esc_url), written as it is.

methodremoveAttribute(string $name): void

Records a removal of an attribute the source has.

methodcancelAttribute(string $name): void

Forgets a pending set that never reached the source: cancelling an addition is not a removal.

methodattribute(string $lower): ?array

The pending set or removal for a lowercase name, or null when there is none. @return array{name: string, value: string|true|null}|null

@return array{name: string, value: string|true|null}|null
methodaddClass(string $class): void

Records a class to add; a later removal of the same class wins.

methodremoveClass(string $class): void

Records a class to remove; a later addition of the same class wins.

methodhasClassEdits(): bool

Whether any class edit is pending.

methodhasTagEdits(): bool

Whether any attribute or class edit is pending.

methodclassesAfter(array $base): array

The class list after the pending edits: additions appended once, removals gone.

@param list<string> $base
@return list<string>
staticclosesComment(string $text): bool

Whether text holds a comment closer, "-->" or "--!>".

methodsetTextFor(?string $type, ?string $tagName, ?string $commentType, string $text): bool

Records a text replacement for a token of a kind that takes one: text (escaped), an ordinary HTML comment, or a raw-text element's body (script and style bodies get their closers escaped). False when the kind takes none, or the text would end the element early.

methodtakeText(): ?string

The pending text replacement, taken: it is cleared on read.

methodtakeReplacements(array $attributes, int $nameEnd, ?string $baseClassValue): array

The source replacements the attribute and class edits amount to, and forgets them: a changed attribute is rewritten in place (its duplicates removed), a removed one is cut, a new one is inserted after the tag name (several in the order of their text).

@param list<array{name: string, lower: string, start: int, end: int, value: ?string}> $attributes the tag's own
@return list<array{int, int, string}> start, end, text
methodclear(): void

Forgets everything pending.

staticsplice(string $html, array $replacements, array $bookmarks, int $tokenStart, int $tokenEnd): array

Splices replacements into the source, ascending, and moves the bookmarks and the cursor by what the token at [$tokenStart, $tokenEnd] gained or lost.

@param list<array{int, int, string}> $replacements
@param array<string, array{0: int, 1: int}> $bookmarks
@return array{0: string, 1: array<string, array{0: int, 1: int}>, 2: int} the source, the bookmarks, the cursor
staticclassValue(?array $pending, array $attributes): ?string

The class attribute's decoded value before class edits: a pending set, else the source's; null when there is none.

@param array{value: string|true}|null $pending
@param list<array{lower: string, value: ?string}> $attributes
staticclassList(?array $pending, array $attributes): array

The class names before class edits.

@param array{value: string|true}|null $pending
@param list<array{lower: string, value: ?string}> $attributes
@return list<string>
insiderebuiltClassValue(), existingName(), escape()

Escaped final readonly class

public/minn/src/Minn/Html/Escaped.php · 6 lines · Open the class →

An attribute value that comes already escaped (a URL through esc_url), written as it is.

new__construct(string $value)
propreadonly string $value

Scanner final class

public/minn/src/Minn/Html/Scanner.php · 291 lines · Open the class →

Reads one token's shape out of raw HTML at an offset: a tag with its name and attributes, a comment of one of the reference's kinds, a doctype, or a processing instruction. Pure and stateless; null means the input ends before the token does, which is where a streaming reader pauses.

statictag(string $html, int $at, int $nameStart): ?array

A tag starting at $at, its name from $nameStart (one byte in for an opener, two for a closer): the name, where it ends, the end offset, the attributes with their source offsets, and the self-closing flag.

@return array{name: string, nameEnd: int, end: int, attributes: list<array{name: string, lower: string, start: int, end: int, value: ?string, quoted: bool}>, selfClosing: bool}|null
staticrawText(string $html, string $name, int $from): ?array

A raw-text element's body (script, style, textarea, ...): from $from to its closing tag, or null when the closer never comes.

@return array{textStart: int, textLength: int, end: int}|null
staticmarkupDeclaration(string $html, int $at): ?array

What "<!" opens at $at: an HTML comment (abruptly closed or not), a doctype, a CDATA lookalike, or a bogus comment.

@return array{kind: string, start: int, end: int, textStart: int, textLength: int, commentType?: string, fullStart?: int, fullLength?: int}|null
staticquestion(string $html, int $at): ?array

What "<?" opens at $at: a processing instruction when an ASCII alphanumeric target (not "xml") is followed by space, "?" or ">" (its text after the space, up to "?>" or ">"); a PI lookalike comment for another XML name closed by "?>"; otherwise an invalid comment.

@return array{kind: string, start: int, end: int, textStart: int, textLength: int, target?: string, commentType?: string, fullStart?: int, fullLength?: int}|null
staticattributeValue(array $attributes, string $lower): string|true|null

A source attribute's decoded value (true when it has none), or null when the tag lacks it.

@param list<array{lower: string, value: ?string}> $attributes
staticopensMarkup(string $html, int $at): bool

Whether the "<" at $at opens markup: a tag, a closer, "<!" or "<?".

statictextEnd(string $html, int $at): int

Where text starting at $at ends: at the next "<" that opens markup, before a "<" ending the input, or at the end.

staticcdata(string $html, int $at): ?array

A CDATA section in foreign content, "<![CDATA[" to "]]>"; null when it does not close.

@return array{kind: string, start: int, end: int, textStart: int, textLength: int}|null
staticdoctype(string $body): array

A doctype's body split into its name and public and system identifiers.

@return array{name: ?string, public: ?string, system: ?string}
staticisSpace(string $c): bool

Whether a byte is HTML whitespace.

insideattribute(), comment()

TagQuery final readonly class

public/minn/src/Minn/Html/TagQuery.php · 37 lines · Open the class →

A tag processor's next_tag() query: a tag name (any case), a class, the nth match to stop at, and whether closers count.

propreadonly ?string $tagName
propreadonly ?string $className
propreadonly int $offset
propreadonly string $closers
staticfrom(array $query): self

The query read from the array a caller passes.

@param array{tag_name?: ?string, class_name?: ?string, match_offset?: int, tag_closers?: string} $query
methodmatches(string $name, int $closer, callable $hasClass): bool

Whether a tag (by name, closer or not, and a class test) matches, before the offset is counted.

Tags final class

public/minn/src/Minn/Html/Tags.php · 589 lines · Open the class →

A streaming HTML tokenizer with in-place edits: every token in document order, attributes decoded, edits applied to the source without reserialising the rest, bookmarks to seek back to (probe html-tag-processor).

constTAG = '#tag'
constTEXT = '#text'
constCOMMENT = '#comment'
constDOCTYPE = '#doctype'
constPI = '#processing-instruction'
constFUNKY = '#funky-comment'
constCDATA = '#cdata-section'
constPRESUMPTUOUS = '#presumptuous-tag'
constCOMMENT_HTML = 'COMMENT_AS_HTML_COMMENT'
constCOMMENT_ABRUPT = 'COMMENT_AS_ABRUPTLY_CLOSED_COMMENT'
constCOMMENT_INVALID = 'COMMENT_AS_INVALID_HTML'
constCOMMENT_CDATA = 'COMMENT_AS_CDATA_LOOKALIKE'
constCOMMENT_PI = 'COMMENT_AS_PI_NODE_LOOKALIKE'
constRAW_TEXT = array ( 0 => 'script', 1 => 'style', 2 => 'textarea', 3 => 'title', 4 => 'xmp', 5 => 'iframe', 6 => 'noembed', 7 => 'noframes', )

Elements whose body is raw text up to the closer.

new__construct(string $html)
methodnextToken(): bool

Advances to the next token; false at the end or at an incomplete one.

methodnextTag(array $query): bool

Advances to the next tag matching the query; false when none. @param array{tag_name?: ?string, class_name?: ?string, match_offset?: int, tag_closers?: string} $query

@param array{tag_name?: ?string, class_name?: ?string, match_offset?: int, tag_closers?: string} $query
methodpausedAtIncompleteToken(): bool

Whether the scan stopped inside an unfinished token.

methodtokenType(): ?string

The current token's kind.

methodtokenName(): ?string

The current token's name.

methodtag(): ?string

The current tag name, upper-cased; a processing instruction's target, or a PI lookalike's, as written.

methodisCloser(): bool

Whether the current tag is a closer.

methodselfClosing(): bool

Whether the current tag has the self-closing flag.

methodcommentType(): ?string

The current comment's kind.

methodfullCommentText(): ?string

The current comment's whole text (a funky comment's is its text).

methodmodifiableText(): string

The current token's text as a reader sees it.

methoddoctype(): ?array

Doctype pieces, or null when the token is not a doctype. @return array{name: ?string, public: ?string, system: ?string}|null

@return array{name: ?string, public: ?string, system: ?string}|null
methodattribute(string $name): string|true|null

The decoded value, true for a bare attribute, null when absent; a pending edit answers first.

methodattributeNames(string $prefix): ?array

The current tag's attribute names with a prefix, lower-cased, in source order; null off a tag. @return list<string>|null

@return list<string>|null
methodclasses(): array

The current tag's classes. @return list<string> distinct class names after pending edits

@return list<string> distinct class names after pending edits
methodhasClass(string $class): bool

Whether the current tag has a class.

methodsetAttribute(string $name, Minn\Html\Escaped|string|int|float|bool|null $value): bool

Sets an attribute on the current tag.

methodremoveAttribute(string $name): bool

Removes an attribute from the current tag.

methodaddClass(string $class): bool

Adds a class to the current tag.

methodremoveClass(string $class): bool

Removes a class from the current tag.

methodsetModifiableText(string $text): bool

Replaces the current token's text, when its kind allows it.

methodsetBookmark(string $name): bool

Names the current token's position.

methodreleaseBookmark(string $name): bool

Forgets a bookmark.

methodhasBookmark(string $name): bool

Whether a bookmark exists.

methodseek(string $name): bool

Moves to a bookmark.

methodparseAs(string $namespace, string $tagBodies = 'html'): void

What follows as a namespace's content: outside HTML "<![CDATA[" opens a section; tag bodies read as HTML ("html", raw text elements whole) unless foreign ("foreign").

methodallowBookmarks(int $max): void

Raises how many bookmarks may be held at once.

methodrewind(): void

Back to the start of the document, with every update written in and the bookmarks kept.

methodsplitText(int $length): void

The current text token cut to its first bytes; the rest is read next.

methodtokenSpan(): array

Where the current token sits in the document: [start, end].

methodtextSpan(): array

Where the current text token's text sits: [start, length].

methodsource(int $start, int $length): string

The source between two offsets, updates written in.

methodbookmarkSpan(string $name): ?array

Where a bookmark sits: [start, end], or null when there is none of that name.

methodreplaceAfter(int $start, int $end, string $text): void

Replaces a span at or after the current token's end, updates written in first: the new text is read next, and bookmarks past the span move with it.

methodhtml(): string

The document with every update written in.

insidesetToken(), resetToken(), takeTag(), take(), baseClassList(), baseClassValue(), flush(), applyReplacements(), rescanCurrent(), rescan()

TokenMap final class

public/minn/src/Minn/Html/TokenMap.php · 87 lines · Open the class →

A map from words to replacements that reads the longest word at a place in a text (as character reference names are read): words shorter than the key length stand alone, the others are grouped by their first bytes, longest first.

new__construct(array $mappings, int $keyLength)
@param array<string, string> $mappings
methodkeyLength(): int

How many leading bytes group the words.

methodcontains(string $word, string $caseSensitivity): bool

Whether the word is in the map ("case-sensitive" or "ascii-case-insensitive").

methodread(string $text, int $offset, string $caseSensitivity): ?array

The replacement for the longest word at $offset, and the word's length; null when none starts there.

@return array{0: string, 1: int}|null
methodtoArray(): array

Every word and its replacement: the short words first, then each group longest first.

@return array<string, string>
insidesame()