Non-breaking space vs space: U+00A0 and
It looks like a space, it acts like a space when you read it, and your code insists it is something else. It probably is: a non-breaking space.
The two spaces
| Ordinary space | Non-breaking space | |
|---|---|---|
| Code point | U+0020 | U+00A0 |
| HTML | a real space, or   | or   |
| Line wrapping | The browser may break the line here | The browser should not break the line here |
| Looks like | A gap | The same gap |
" " === char | true | false |
There are other space-like characters too (thin space, figure space, ideographic space) but U+00A0 is the one you will meet every week.
Where it comes from
- HTML authors write
to keep “10 MB”, “£4.50” or a name together on one line. - Word and Google Docs insert non-breaking spaces around punctuation and copied numbers.
- Excel uses them as thousands separators in some locales, or when you paste from a formatted cell.
- PDF and OCR often turn visual gaps into
U+00A0.
What breaks
"10\u00A0MB".split(" ") // ["10\u00A0MB"] — one piece, not two
"10\u00A0MB".replace(" ", "") // "10\u00A0MB" — nothing removed
"10\u00A0MB".replace(/\s/g, "") // "10MB" — \s does match NBSP
"10\u00A0MB".split() # Python: split() with no argument *does* split on NBSP
"10\u00A0MB".split(" ") # Python: ["10\u00A0MB"] — literal space only
| Operation | Ordinary space | NBSP |
|---|---|---|
JavaScript / Python trim() / strip() | Removed at the ends | Removed at the ends |
Regex \s (JS; Python 3 without re.ASCII) | Matches | Matches |
Literal " " in split / replace | Matches | Does not match |
Python str.split() with no arguments | Splits | Splits |
CSS white-space wrapping | May wrap | Should stay together |
That last row is why people use it on purpose. The rows above are why a CSV parser, a “split on space” tokenizer or a unit test then fails on text copied from a web page.
How to normalise it
text = text.replace("\u00a0", " ") # Python
text = text.replace(/\u00A0/g, " ") // JavaScript
# or replace every Unicode space separator with an ordinary space:
import unicodedata, sys
text = "".join(" " if unicodedata.category(c) == "Zs" else c for c in text)
Paste the two “identical” lines into the text diff to see the NBSP show up as a change. The word and character counter counts it as a character. For a named report (it flags spaces and invisibles separately) use Anvil /v1/text/inspect on non-secret text.
A zero-width space is a different problem: it is not a space at all, and trim() will not remove it. See invisible zero-width characters.
Related
SMS counter: find non-breaking spaces that cost you a text · Invisible zero-width characters · Strings that look the same but are not equal · Invisible characters in a password · Look-alike (homoglyph) characters · Text diff · Wirebook: invisible characters
Questions
What is a non-breaking space?
Unicode character U+00A0, written in HTML as . It looks like a normal space but stops the browser breaking the line at that point, and it is a different character in code.
Why does replace(' ', '') not remove a space I can see?
That space is probably U+00A0, not U+0020. replace(' ', '') only removes ordinary spaces. Use a regex \s or an explicit replace of \u00A0.
Does trim() remove non-breaking spaces?
Yes. JavaScript trim() and Python strip() treat U+00A0 as whitespace, so they remove it from the ends. They will not remove a zero-width space.
Why do Word and Excel insert non-breaking spaces?
Word processors use them to keep numbers, units and names together on one line. Copying out of a document or a formatted spreadsheet cell brings them with you.
How do I find non-breaking spaces in a file?
Search for \u00A0 or \xa0 in your editor, or paste into the text diff against a version typed by hand. In Python, '\\u00a0' in repr(s) gives them away.
Published · updated .