Developer Tools

Text Cleaner: Getting the Junk Out of Pasted Text

Pasted text carries more than words: Word markup, PDF line breaks, tracking parameters, and a set of characters that render as nothing at all. What each one actually is, why WordPress makes some of it worse, and what to strip.

Text Cleaner: Getting the Junk Out of Pasted Text

A paragraph pasted out of a client’s Word document looks correct in the editor and wrong on the published page: slightly smaller, slightly greyer, set in a font that appears nowhere else on the site. No theme setting fixes it, because the problem is not in the theme. It is sitting in the post content, where the paste left a span carrying an inline font size and a Word class name, and an inline style beats your stylesheet every time.

Text copied out of a PDF fails differently. A PDF has no paragraphs, only lines of positioned glyphs, so every visual line end arrives as a real line break and a single sentence lands as four separate lines. Words hyphenated at the column edge arrive split down the middle: para- on one line, graph on the next.

Then there is the half of the problem you cannot see at all. Non breaking spaces from a chat app. A zero width joiner from an AI answer. A soft hyphen a word processor inserted years ago. A byte order mark that should have been consumed by whatever read the file. Each one occupies a position in the string, changes how a line wraps or whether a phrase search matches, and the editor draws it as nothing.

None of this is a bug. Word, Google Docs, PDF viewers and chat apps all put a rich and perfectly correct version of their own content on the clipboard. It is just a different format from the one you wanted, and WordPress stores what it is handed.

The tool below strips all of it. Paste, choose what to remove, copy the result out. Everything runs on the string inside the page: nothing is uploaded and no request is made, so a paste from an unpublished draft or a client contract never leaves your machine.

Text cleaner

Paste anything, pick what should go, press Clean. Nothing is uploaded: the text never leaves this browser tab.

Structure
Markup and links
Characters
Capitalisation
Nothing pasted yet
WunderPaint
The Dynamic Design and Automation Studio
WunderPaint is a layered image editor for your WordPress media library. WunderPaint Studio is the same thing in any browser, free and without an account.

Where the junk comes from

Word and Google Docs put HTML on the clipboard, not text. Word’s flavour carries class names it invented for its own styling model plus inline styles on every run of characters, so one sentence can arrive wrapped like this.

<p class="MsoNormal">
  <span style="font-size:11.0pt;font-family:'Calibri',sans-serif">
    The sentence you actually wanted.
  </span>
</p>

Google Docs is tidier but not clean. It wraps the selection in a b element with font-weight:normal and a generated id, which is why bold sometimes refuses to apply to text pasted from Docs. The block editor knows about that particular wrapper and removes it, which tells you something useful: the normalisation is a list of known cases, not a general guarantee.

Web pages give you anchors, and anchors bring their query strings. Copy a sentence containing a link from a newsletter or a search result and the href arrives with utm_source, utm_campaign and sometimes a click tracker in front of the real address.

Chat apps contribute the quiet damage. Slack, WhatsApp, iMessage and the macOS text system substitute curly quotes as you type and insert non breaking spaces to stop short phrases wrapping. AI output has its own signature: real em dash characters rather than hyphens, a single ellipsis character rather than three dots, and now and then a zero width joiner left over from an emoji sequence someone edited.

It matters most when the text is heading somewhere with no tolerance for surprises. A filename. An alt attribute. A spreadsheet column that feeds a batch of generated images, where one bad cell becomes two hundred bad files.

Table of six paste sources, from Word to AI output, listing what each one adds to the text and whether it is visible in the WordPress editor.

The characters you cannot see

A non breaking space, U+00A0, renders exactly like a space and forbids a line break at that point. It is two bytes in UTF-8, C2 A0, and it is written &nbsp; as an entity. Paste a paragraph full of them into a narrow column and you get a line that refuses to wrap and pushes past its container. It also breaks any search that expects an ordinary space.

A zero width space, U+200B, renders as nothing and permits a break. It is used legitimately to suggest break points inside long URLs. Pasted into prose it is invisible sabotage: a search for “media library” will not match a phrase with a zero width space wedged between the two words, and neither will a database query, a redirect rule or a duplicate check.

A soft hyphen, U+00AD, is a conditional hyphen. It stays invisible until the renderer decides to break the line at that spot, at which point a hyphen appears. That is why a paragraph copied from a justified layout can sprout hyphens inside words after you paste it into a different column width. The character was always there. Only the line width changed.

The rest are variations on nothing. A zero width joiner, U+200D, exists to glue emoji parts into a single glyph; alone in a sentence it is pure noise. A word joiner, U+2060, is a zero width character that forbids a break. A byte order mark, U+FEFF, is a file header meant to tell a reader which encoding follows; when the reader does not consume it, it lands at the front of your content and shows as nothing or a stray box.

Mojibake, and where it’s comes from

A sequence of bytes is not text until something decides which table to read it with. UTF-8 encodes the right single quotation mark, U+2019, as three bytes: E2 80 99. Read those same three bytes one at a time through Windows-1252, the single byte table people still loosely call Latin-1, and each byte becomes its own character: â, , . That is the whole of the famous it’s. Not corruption, a misreading.

The same arithmetic explains every variant. The ellipsis character, E2 80 A6, becomes …. The left double quotation mark, E2 80 9C, becomes “. A non breaking space, C2 A0, becomes  followed by a space, which is why a stray  keeps appearing in front of prices, units and degree signs. Once you recognise the †prefix you can read the rest by eye.

In WordPress the wrong table usually gets chosen in one of four places: a database dump exported as one charset and imported as another, a DB_CHARSET value in wp-config.php that no longer matches what the tables hold (modern installs ship utf8mb4), a plugin reading a CSV or a feed without declaring an encoding, or a response served without a charset in its Content-Type header so the browser guesses.

It also stacks. Save mojibake back through a UTF-8 pipeline and the wrong characters are encoded again, so ’ becomes ’. The repair is the reverse of the damage: encode the text back to Windows-1252, decode it as UTF-8, repeat while the result stays valid UTF-8. That is why a repair pass sometimes has to run more than once on the same paragraph.

Table of six invisible Unicode characters with code points and effects, plus a code panel showing UTF-8 byte sequences misread as Windows-1252.

What WordPress adds on top

The first thing to understand is that WordPress rewrites your punctuation on the way out. wptexturize(), in wp-includes/formatting.php, is attached to the_title, the_content, the_excerpt, comment_text and several more in wp-includes/default-filters.php. It is an output filter. The database keeps your straight quotes; the page shows curly ones. The substitutions are worth knowing precisely, because two of them surprise people.

  • Three hyphens become an em dash.
  • Two hyphens with a space on each side become an em dash.
  • Two hyphens with no spaces become an en dash, unless they follow xn, so internationalised domain names survive.
  • A single hyphen with a space on each side becomes an en dash. This is the one that catches people out: a plain typed hyphen between two words is silently promoted.
  • Three dots become one ellipsis character, and (tm) becomes a trademark sign.
  • Straight quotes become curly, with a long list of special cases for apostrophes in words like 'em and 'tis and years such as '99.

The pass skips anything inside pre, code, kbd, style, script and tt, and inside the [code] shortcode. Those defaults are filterable through no_texturize_tags, and the whole thing can be switched off with the run_wptexturize filter. The practical consequence: converting curly quotes to straight ones in your source text does not stop curly quotes appearing on the published page. Usually that is what you want. What you do not want is to fight it without knowing it is there, which is one of the quieter items on the list of typography mistakes that give an amateur design away.

The second thing is that the block editor stores markup rather than re-deriving it. Its paste handler normalises a good deal: Word’s fake lists become real ones, stray divs are flattened, the Google Docs wrapper goes. What survives is written into post_content as literal HTML and is what your theme lives with from then on. Paste into a Classic block instead and nothing is normalised at all.

The third thing is that kses will not save you. WordPress runs wp_kses on post content only for users lacking the unfiltered_html capability, which on a single site install means everyone except administrators. Even for those users, class and style both sit in _wp_add_global_attributes() in wp-includes/kses.php, so <span style="font-size:11.0pt"> is on the allowed list by design. Word’s inline styles pass the filter cleanly.

The fourth thing is the important one. None of the invisible characters are markup. They are text. No paste handler, no kses pass and no texturize rule touches a zero width space, because to all of them it is an ordinary character in an ordinary string. The same goes for the fields around your posts: a trailing non breaking space pasted into an alt attribute is stored exactly as pasted, and since alt text lives in more than one place, you can end up cleaning it twice.

What to strip and what to keep

Removing every line break is almost always wrong. The option exists for real cases: a single form field, a filename, an alt attribute, one spreadsheet cell. Applied to prose it destroys the only structure the text has left. The PDF problem is not that there are line breaks, it is that the line breaks are in the wrong places.

What you want instead is to join lines inside a paragraph and leave the blank line between paragraphs alone. That is why joining and collapsing are separate operations: a blank line is a paragraph boundary and carries meaning, a lone line break inside a run of text is a wrapping artefact and carries none. Join across blank lines and four paragraphs become one wall of text that later editing never fully recovers.

Repairing split words carries the same shape of risk. It correctly rejoins para- and graph. It cannot know that well- and known were a genuine compound that happened to fall at a line end. Run it on PDF text, where nearly every line end hyphen is an artefact, and leave it off for text that already wraps correctly, where nearly every one is real.

Dropping duplicate lines is excellent on a list merged from two sources and ruinous on prose with legitimately repeated short lines. Note the order: duplicates go before the line joining, so with both switched on the raw lines are deduplicated first and the paragraphs rebuilt afterwards.

The two link options are not interchangeable. Unlinking keeps the words and drops the address, which suits a quoted sentence. Removing bare URLs deletes the address itself, which suits a pasted forum reply where the URL sits on its own line doing nothing.

Quote direction depends entirely on where the text is going. Convert curly to straight for anything headed into code, a filename, a CSV or a design tool with no smart quote logic of its own. Convert straight to curly for text that will not pass through a typographic filter later, such as a caption you are about to bake into an image, where the punctuation is part of the artwork. Accent normalisation follows the same logic: right for a slug, wrong for the body of a German or French article, where stripping diacritics changes the words.

How to check what you actually have

The cheapest instrument is the count. Characters, words and lines, before and after. If the character count drops by forty and nothing visible went missing, you removed forty invisible characters. If the line count falls from 180 to 12, you turned a PDF column back into twelve paragraphs. If the word count moves when you only meant to fix quotes, something joined that should not have.

Inside WordPress, the block editor’s code editor view spells out &nbsp; and shows every pasted span in plain sight. It will not reveal zero width characters, because there is nothing to reveal. Those need a search rather than a look.

# count every invisible character in a file, by kind
grep -oP '[x{00a0}x{00ad}x{200b}-x{200d}x{2060}x{feff}]' text.txt | sort | uniq -c

The same idea works against the posts table. Both queries below are read only, and both assume the default wp_ prefix, so substitute your own.

# published posts containing a non breaking space
wp db query "SELECT ID, post_title FROM wp_posts
             WHERE post_status = 'publish'
               AND post_content LIKE CONCAT('%', UNHEX('C2A0'), '%')
             LIMIT 20;"

# published posts carrying the classic mojibake signature
wp db query "SELECT ID, post_title FROM wp_posts
             WHERE post_content LIKE '%â€%'
             LIMIT 20;"

Run the second one first. If it returns forty rows you have an encoding problem, not a paste problem, and fixing posts one at a time will not touch the cause.

Where a text cleaner stops

It cleans text, so formatting does not survive. Bold, italics, links and headings come out as words. That is the right trade for killing a Word paste, but it fixes the order of work: clean on the way in, then format in the editor. Clean afterwards and you format twice.

It does not repair a database. If your published posts already contain mojibake, an encoding repair fixes the paragraph in front of you, not the two hundred rows behind it. That job belongs to a corrected export and import, or to fixing the declared charset, and it deserves a backup first.

It has no find and replace and no regular expressions: every operation is a named transformation with a defined scope, which is deliberate. And it has no opinion about your language. German low quotes and French guillemets are typographic conventions, not encoding rules, and no general purpose cleaner should guess at them. The case modes are mechanical too, so neither knows that wp-config.php stays lower case.

Pasted text is two problems stacked on top of each other, and most people only ever notice the first. The visible one is structure: markup that came along for the ride, line breaks in the wrong places, links carrying campaign parameters. The invisible one is characters, and it is the one that quietly costs you, because a zero width space in a heading never announces itself. It just means a search does not match, a duplicate check misses, a line wraps somewhere absurd on a phone.

The fix is not vigilance, it is a boundary. Decide that text entering your site passes through one cleaning step, always, whatever it came from. The question then stops being “is this paste dirty” and becomes “what does this particular text need”. A PDF needs line joining and split word repair. An AI draft needs its dashes and ellipses normalised. A chat message needs its non breaking spaces turned back into ordinary ones. A Word document needs its markup taken away entirely.

After that, let WordPress do its own job. wptexturize supplies the curly quotes on output whether or not you supplied them on input, so the version you store can stay plain and predictable while the version readers see is properly typeset. Clean text in the database, correct typography on the page, and nothing invisible in between.

Text Cleaner: Getting the Junk Out of Pasted Text

Table of Contents

Learn it by building something

Every week one thing you can make the same afternoon, from dynamic templates to 3D type. Written down step by step.

One mail a week, and then it ends.
Unsubscribe in one click.

Speed & Performance

A Web Performance Budget for Your WordPress Site

Pick a connection profile and a target load time, and arithmetic hands you a byte budget per resource class. A web performance budget is a decision tool: it tells you which image, which font and which plugin gets told no, before the page ever gets slow.

SEO & Structured Data

Redirect Rule Generator: The Rules That Never Fire

A redirect list rarely breaks. It accumulates: a broad prefix rule that kills every rule below it, a target missing a trailing slash that doubles your hop count, a 301 where a 308 was needed. Generate the Apache, nginx and CSV versions of your rules, then walk a URL through them and watch where it actually goes.

WordPress Images

Image File Names: The Boring Detail That Breaks Things

WordPress renames every file you upload, using rules almost nobody has read. Here is what sanitize_file_name actually changes, why a name with an umlaut resolves on one host and 404s on another, and why core answers a collision by adding a number instead of overwriting. Clean your names in the browser first, because after the upload the name is a URL.

Developer Tools

JSON Validator and Repair: What the Parser Actually Saw

Parsers name the character after the mistake, and PHP names nothing at all. The whole JSON grammar, the invisible bytes that break a file that looks perfect, the places WordPress hides JSON, and a validator that reports every fault in one pass.

SEO & Structured Data

WordPress OG Image: The Shop Window You Never See

WordPress core never writes an og:image tag, so a wrong or missing link preview is always somebody else's output. Here is the fallback chain a plugin walks, the registered size trap that makes a valid tag point at a missing file, and two curl commands that settle it.

Speed & Performance

WordPress Media Library Slow: What Actually Causes It

A media library that takes thirty seconds is not one slow thing. It is a database query, a metadata prime, a JSON build in PHP and eighty image requests, stacked on top of each other. Here is how to measure which one is hurting your site, and the fix order that actually moves the number.

Download the free WunderPaint Plugin for WordPress

The WunderPaint workspace with the layers panel, adjustment sliders, text style presets and the asset library along the bottom

The Image Editor & Design Studio

Everything described here can be done in the browser, on your own site. The live demo runs the full editor with nothing to install.

Free

Chaos Art

Autonomous painters make one-of-a-kind abstract art in 3D space - gestures, art movements, painterly media, and embeds that paint a new original for every visitor.

Pro

Particle Strokes

Paint with swarms of light: twenty-two movements, a stamp you draw yourself, and curves that give a stroke a shape - the swarm keeps painting for a few seconds after you let go.

Pro

City Diorama

Any place on earth as a miniature you could hold: real streets, water and building footprints raised into a 3D diorama - or wrapped around a sphere as your own tiny planet.