Skip to content

Simplify Paragraph#substitute - #201

Merged
satoryu merged 2 commits into
masterfrom
refactor-paragraph-substitute
Oct 17, 2026
Merged

satoryu merged 2 commits into
masterfrom
refactor-paragraph-substitute

Conversation

@satoryu

@satoryu satoryu commented Oct 17, 2026 •

Copy link
Copy Markdown
Member

Why

After #199, Paragraph#substitute did everything in one ~40-line loop do … break … end:

  • collect the w:t nodes and compute their offsets
  • find the match and skip empty matches
  • work out which w:t the match spans
  • rewrite them

That made it hard to see what the method does.

What

The method body is now just "find the next match, replace it, move on":

def substitute(pattern, replacement)
  search_from = 0
  while (match = find_match(pattern, search_from))
    replaced = match[0].sub(pattern, replacement)
    replace_text_range(match.begin(0)...match.end(0), replaced)

    # advance past the inserted replacement so it is not re-matched
    search_from = match.begin(0) + replaced.length
  end
  self
end

with private helpers:

  • find_match(pattern, from): the next match in the paragraph text, or nil. An empty match also gives nil, the same as the previous break.
  • replace_text_range(range, string): replaces a range of the paragraph text across its w:t nodes. A single boundaries array (texts[i] spans boundaries[i]...boundaries[i + 1]) replaces the separate offsets and length arithmetic, and the single-node case returns early.
  • text_nodes: the paragraph's w:t in document order.

No behavior change. The only cost is that the w:t XPath runs twice per match instead of once.

Verification

🤖 Generated with Claude Code

satoryu and others added 2 commits October 18, 2026 02:39
Split the substitution loop into private helpers so #substitute reads
as "find the next match, replace it, move on":

- find_match returns the next non-empty match of the paragraph text
- replace_text_range replaces a range of the paragraph text across its
  w:t nodes (using one boundaries array instead of offsets + lengths)
- text_nodes collects the paragraph's w:t in document order

No behavior change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A mutation check showed no spec failed when the guard against empty
matches was removed, although without it a pattern like /x*/ loops
forever. Pin the existing behavior, under a timeout so a regression
fails instead of hanging.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@satoryu
satoryu merged commit 3f41174 into master Oct 17, 2026
6 checks passed
@satoryu
satoryu deleted the refactor-paragraph-substitute branch October 17, 2026 17:46
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