✨ feat(clean): transform trees before serialization - #797
Open
gaborbernat wants to merge 6 commits into
Open
Conversation
Clean whitespace before traversal without serializing and parsing again. Keep native mutators composable with existing sanitizer and linkifier callables, with explicit ownership and exception behavior. Add inner serialization so callers can retain formatting options when emitting children. Cover the new operations in the shared benchmark suite and publish only the feeds consumed by the documentation. Refs tox-dev#791
Merging this PR will not alter performance
Performance Changes
Comparing Footnotes
|
Match neighboring module signatures so PyPy does not expose an extra module argument. Use Py_NewRef for the owned pipeline root, keeping GCC refcount implementation branches outside transformer coverage. Validate signature parity on PyPy and all transformer branches with GCC.
Scan borrowed text before allocating collapsed output. Leave unchanged source spans intact and retain old character data when observers need it. This preserves lossless spelling on no-op calls and removes the retained UCS4 input copy for unobserved mutations. The 1 MiB unchanged case allocates no text buffer and runs about 23% faster. The whitespace-heavy case retains 60% less memory and runs 49% faster. Extend the benchmark suite and CodSpeed with both cases, and refresh the feeds used by the documentation.
Track layout and encoding costs in the shared pyperf and CodSpeed suite. Keep the measured feeds in the performance guide so readers can inspect child output and composition costs. Document tree cleanup across tutorials, task recipes, API reference, and ownership explanations, including XML fragment export and custom steps.
Compare parsed-tree cleanup and child output with library APIs so migration guides show both costs and policy differences. Keep fresh cyclic trees out of later benchmark iterations and retain prior migration measurements when adding the new rows.
Reserve each indentation run once and fill it from a growing prefix instead of checking capacity and copying once per tree level. Alternating release builds reduce indented string time by 6.5-17.8% across four pages and streaming time by 6.8-18.1%, preserving output and overflow checks. Compare native dispatch with a Python implementation of the same Node/None contract. Refresh the docs-consumed performance and migration feeds, and cover indentation boundaries, encoding errors and comparator behavior.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Callers converting HTML to XML in #791 need collapsed whitespace before traversing the tree. Minifying during serialization leaves the stored text unchanged, so they must serialize and parse again before reading normalized XML fragments. ✨ Allow cleanup on the existing tree and configurable child output without a wrapper, removing that extra round trip from this workflow.
Compose cleanup with
clean.transform_node(node, *steps)using ordinary synchronous callables. Applications can mix functions, bound methods, and callable objects without registration or a transformer base class. Each step receives the current root once and owns any descendant traversal it needs. Returning aNodeselects the next root; returningNoneretains it, supporting existing mutators. Invalid results raiseTypeErrorwith the stage number, and a pipeline with no steps returns its input.Make ownership explicit when combining stages.
collapse_whitespace_node,strip_comments_node, and the existinglinkify_nodemutate and return the same root;sanitize_nodereturns a copy. Callers must retain the composed result when a step can replace the root. Exceptions stop execution and preserve earlier mutations. Native operations lock their own tree, but composition holds no lock across callbacks and provides no transaction across stages. Applications must coordinate shared-tree access and choose stage order with care: a custom edit after sanitization can introduce content outside the sanitizer policy.Whitespace cleanup collapses ASCII whitespace runs to one space, including leading and trailing runs, while retaining NBSP and other Unicode spaces. Preserve preformatted and raw-text contexts, check ancestors for subtree calls, and skip foreign subtrees. Visit template content without entering attached shadow trees. Reject XML-mode trees because their consumers may assign significance to whitespace; CSS layout rules remain outside this HTML policy. Comment removal supports HTML and XML, retains the context root, and leaves references to removed comments valid as detached nodes. Removing comments before collapsing whitespace can join runs separated by a comment.
Add keyword-only
inner=TruetoNode.serialize,Node.encode, andNode.serialize_iter. Retain the selected node as serialization context while omitting its wrapper, so raw-text and preserved-whitespace rules survive child output. Existing calls keep outer serialization. Compact and indented output support streaming; minified output usesserializeorencode. A leaf root produces empty inner output. Callers needing well-formed XML fragments should continue to useinner_xml.Keep dispatch and tree mutation in C, with Python providing exports and typing. Traverse without creating a Python wrapper for each visited node, preserve text-node identity, and retain borrowed source storage when text needs no change. Changed text uses replacement storage and preserves mutation-observer old values. Indented output reserves each indentation run once and fills it by doubling the copied prefix, avoiding a capacity check and small copy at each depth. Tree arenas retain allocations until the caller releases the tree, so cleanup does not promise to reduce the resident memory of a long-lived tree.
Document the workflow across tutorials, how-to recipes, reference contracts, and the ownership explanation, with migration guidance for 21 libraries. Extend the shared benchmark suite and CodSpeed registrations for cleanup, composition, and child output. The performance tables include competitor adapters and the html5lib filter/reparse workflow, with parsing and cyclic-garbage collection outside mutation timings. The dispatcher table includes a Python implementation of the same Node/None contract alongside the unchecked callable loop. They distinguish Python traversal adapters from native operations, record differing preservation and pretty-print policies, and label parse5/jsdom subprocess measurements as Python integration costs. Retain existing migration measurements and commit only JSON feeds consumed by documentation.