Opened 8 years ago
Last modified 4 weeks ago
#43810 new defect (bug)
Apostrophe issue
| Reported by: | colomet | Owned by: | |
|---|---|---|---|
| Priority: | normal | Milestone: | Awaiting Review |
| Component: | Formatting | Version: | 4.9.5 |
| Severity: | normal | Keywords: | wptexturize has-patch has-unit-tests |
| Cc: | Focuses: |
Description (last modified by )
Change History (11)
#3
@
5 years ago
- Description modified (diff)
- Keywords needs-patch added; reporter-feedback removed
- Summary Aphostrophe issue → Apostrophe issue
This can also happen when the letter before the apostrophe is inside a tag (and wptexturize is active).
<strong>I</strong>'m
This ticket was mentioned in Slack in #accessibility by ryokuhi. View the logs.
5 years ago
#7
@
15 months ago
I can reproduce this using block editor.
Screenshots:
Admin: https://shottr.cc/s/16oD/SCR-20250512-psi.png
Frontend: https://shottr.cc/s/1eha/SCR-20250512-ptd.png
Default theme: Twenty Twenty-Five
Steps:
- Add a text block with this text
I'v'e' 'b'e'en'
- Select these letter
'v'e' 'b'e'en'and apply Bold style - Check on frontend
Is this an expected behaviour or not ?
#8
@
11 months ago
After a bit of digging, I am unsure if this is a bug or an expected behaviour. The content is filtered through wptexturize which converts ' (ASCII 39) to open and closed apostrophes based on regex. So the apostrophes do appear correctly when the text is bold. Now, why it is not being done when the content is not between tags like <strong> is another issue.
#9
@
2 months ago
@colomet 's screenshot is really two things. I thought it might be three, but it's these two:
- The apostrophe right after a *closing* inline tag (
<strong>I</strong>'m) curling to an opening quote — that's #18549, and its patch fixes it (https://github.com/WordPress/wordpress-develop/pull/12249), for both'and".
- In @aslamdoctor 's example, the leading
'right after the *opening*<strong>is the same bug mirrored. I checked the actual output:
I've been→I’ve been(correct)I<strong>'ve been</strong>→I<strong>‘ve been</strong>(wrong)
wptexturize loses the preceding I (it's in a separate token) and opens a quote. The realistic case is bolding a contraction. It can be fixed the same narrow way as #18549 — but it needs a check so a genuine opening quote like <em>'Hello'</em> is ignored. (Only treat the ' as an apostrophe when the text before the tag ends in a letter or number.)
On the I'v'e' 'b'e'en' example specifically: comparing plain vs bold text, the *only* apostrophe that changes is the first one — I’v’e’… (plain) vs I‘v’e’… (bold) — which is exactly the case above. Every other apostrophe is identical with and without the bold, so the rest isn't a tag bug. (The ' that opens a quote after a space, 'b, does that in plain text too — that's wptexturize's normal handling of an apostrophe after whitespace.)
This ticket was mentioned in PR #12645 on WordPress/wordpress-develop by @irozum.
4 weeks ago
#10
- Keywords has-patch has-unit-tests added; needs-patch removed
This fixes the "opening tag" half of the bug described on this ticket — the
mirror image of #18549 (PR #12249), which covers the closing-tag case.
wptexturize() splits text on HTML tag boundaries and processes each resulting
token independently. When a token starts with a straight quote/apostrophe
immediately after the *opening* tag of an inline element (e.g. bolding a
contraction), the word before the tag is in a separate token, so the
apostrophe-in-a-word pattern can't see it — the quote is read as being at the
start of a string and curls as an *opening* quote instead:
I<strong>'ve been</strong> → I<strong>‘ve been</strong> (wrong)
I<strong>’ve been</strong> (correct)
This adds a small piece of cross-token state to wptexturize(): whether the
text immediately preceding the current position ends in a letter or digit.
When that's true and the very next tag is the opening tag of a recognized
inline element (a, abbr, b, cite, del, em, i, ins, mark, q, s, small, span,
strong, sub, sup, time, u), a leading apostrophe in the following text token
is treated as continuing that word rather than opening a new quoted phrase.
Block-level tags are deliberately excluded — their boundary already reads as
a break between sentences, so adjacency there doesn't imply word continuation
(e.g. word<div>'ve been</div> still curls as an opening quote). A space
before the tag, or no preceding text at all (e.g. <em>'Hello'</em>), also
keeps the existing opening-quote behavior.
Double quotes are out of scope here — there's no realistic mid-word
continuation case for ", unlike the apostrophe/contraction case this
targets.
Note: the recognized inline-tag list here (`a, abbr, b, cite, del, em, i,
ins, mark, q, s, small, span, strong, sub, sup, time, u`) was chosen
independently of #18549's PR #12249, since that PR hasn't landed and its
exact list isn't available from the ticket discussion alone. Worth
reconciling the two lists if both land.
## Testing
Adds test_apostrophe_after_opening_inline_tag() with 6 cases to
Tests_Formatting_wpTexturize covering: the reported bug, a second inline
tag (<em>), tags nested two deep, the no-preceding-word case, the
space-before-tag case, and a block-level tag. Full Tests_Formatting_wpTexturize
class (363 tests) and the broader formatting group (2077 tests) pass with
no regressions. PHPCS (WordPress-Core ruleset) and PHPStan are clean on the
changed files.
## Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Sonnet 5
Used for: Implementation, tests, and PR description. Reviewed by irozum.
---
This Pull Request is for code review only. Please keep all other discussion
in the Trac ticket. Do not merge this Pull Request. See [GitHub Pull Requests
for Code Review](https://make.wordpress.org/core/handbook/best-practices/pull-requests-code-review/)
in the Core Handbook for more details.
@dpknauss commented on PR #12645:
4 weeks ago
#11
Nice work and good to see this getting some attention! This is the opening-boundary mirror of #18549 (PR #12249), which handles a quote/apostrophe right after a *closing* inline tag (<strong>He</strong>'s, and the trailing quote in '<a>quoted</a>'). Between them they cover both sides of the same context-loss in wptexturize().
On the tag lists differing from #12249's — I'd leave yours as-is rather than force parity. This fix only fires when a word is split across an inline boundary (realistically, bolding an English-language contraction or similar), so the common formatting tags you list cover it. The extra tags #12249 recognizes (bdi, bdo, data, dfn, label, samp, var) earn their place on the *closing* side because possessives attach to inline-wrapped tokens (the <var>x</var>'s value), but there's no matching contraction-continuation case for them after an opening tag. Same reasoning backs scoping " out here. Worth a code comment noting the lists are intentionally different if both land, but not something to reconcile into one list.
On the Copilot notes:
The Unicode one is worth taking. Core's apostrophe-in-a-word pattern is (?<!spaces)' with no ASCII restriction, so café's texturizes correctly today — but the /[a-zA-Z0-9]$/ gate here would regress café<strong>'s</strong>, making the tagged case stricter than the untagged one. The #18549 side handles this with a Unicode fallback (/[\p{L}\p{N}]$/u) behind a fast ASCII path; the same approach would slot in here, plus one test case.
The case/whitespace note I'd leave. This helper mirrors _wptexturize_pushpop_element(), which is itself case-sensitive and splits only on a literal space, so wptexturize() already assumes normalized-lowercase,
space-separated tags throughout (<STRONG> doesn't push/pop no-texturize tags correctly elsewhere either). Tightening only the new helper would make the two parsers inconsistent — better as a separate core-wide change, if at all.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)


Hello thanks for your report. I'm not able to reproduce the issue. can you confirm if this still present, and provide the steps to reproduce it?
Thanks.