JavaScript | Как удалить лишние пробелы в строке? — efim360.ru

JavaScript | Как удалить лишние пробелы в строке?

Какие пробелы считать лишними?

Три случая:

  1. Строка начинается с пробела/пробелов
  2. Строка заканчивается пробелом/пробелами
  3. Внутри строки есть два и более пробелов подряд

Все эти три случая мы будем удалять, очищая строку от лишних пробелов.

ВНИМАНИЕ!!!

Мы рассматриваем пробелы, которые имеют символьные коды № 32. Если вы видите между символами пробел, то это может быть НЕ ПРОБЕЛ, а невидимый символ напоминающий пробел. Таких символов в IT очень много. Просто будьте к этому готовы.

Например. Очень часто на сайтах с WordPress можно встретить последовательность пробелов, где первый имеет символьный код № 160, а второй и последующие № 32. Учитывайте это в своих алгоритмах.

Если вы чего-то не видите, то это ещё не значит, что там ничего нет.

 

Пример строки с "лишними" пробелами

let stroka = "   aa bb  cc   dd    ee     ff     "

В этой строке есть 3 пробела в начале. Есть 5 пробелов в конце. Между словами 1, 2, 3, 4 и 5 пробелов соответственно. Наша задача: избавиться от лишних пробелов.

Получили символьные коды каждого символа из строки - JavaScript
Получили символьные коды каждого символа из строки - JavaScript

 

Решение

stroka.replace(/^ +| +$|( ) +/g,"$1")

ВНИМАНИЕ!!!

Аккуратно копируйте код отсюда т. к. Вордпресс заменяет пробелы из выражения на НЕПРАВИЛЬНЫЕ. Я не нашёл где это можно исправить. Это проблема разработчиков Вордпресс.

Напишите код который видите выше, самостоятельно в редакторе, используя пробел клавиатуры. И у вас всё получится.

 

Видео

 

Как это работает?

Решить задачу нам поможет метод replace(), который наследуется всеми экземплярами строк (String). Внутрь него мы будем передавать два параметра:

  1. Искомое значение - будет регулярное выражение
  2. Заменяемое значение - пустая строка

 

Заменяемое значение

Я хочу начать с определения второго параметра т. к. он должен представлять из себя пустую строку. Мы должны заменять НАЙДЕННОЕ на НИЧТО. В строках нет понятия удаления, есть только понятие "ЗАМЕНА НА НИЧЕГО".

Но в нашем случае в качестве заменяемого значения мы указываем "$1". Это не какой-то там доллар и один. Это часть синтаксиса такой темы как "Подстановки" (substitutions) в регулярных выражениях. Это такая специальная связка для метода replace(), которая помогает создавать группы последовательностей сопоставления внутри последовательностей сопоставления. То есть из всех возможных сопоставлений в строке мы можем выделять отдельные небольшие кусочки сопоставлений.

Ниже в этой публикации попытаемся разобраться в вопросе. Дойдём до сути.

 

Искомое значение

Шаг 1 - определение границ RegExp

Если мы ищем что-то в строке, то нам проще всего использовать самый эффективный инструмент - это регулярное выражение. Они предназначены только для работы со строками и ничем более.

Регулярное выражение поможет отловить любые по длине последовательности из пробелов. Чтобы его правильно написать, нужно хорошо понимать синтаксис шаблона регулярного выражения. То есть нужно знать некоторые термины и уметь ими пользоваться внутри шаблона RegExp.

Начать нужно с оформления границ регулярного выражения. Выглядит это как две косые линии.

//

Сейчас последовательность двух подряд косых линий будет рассматриваться интерпретатором JavaScript, как однострочный комментарий, но мы внутри будем писать наш шаблон и расстояние между косыми линиями будет увеличиваться и это уже не будет комментарием.

 

Шаг 2 - Утверждения

Мы начнём писать шаблон с такого понятия как "Утверждение" (Assertion).

Утверждения (Assertion) в RegExp - JavaScript
Утверждения (Assertion) в RegExp - JavaScript

Внутри шаблона мы будем использовать два "Утверждения" RegExp из JavaScript:

  1. ^
  2. $

Символ крышечки (домика) ^ обозначает начальную границу строки. Если мы справа от символа ^ поставим один пробел, то тем самым мы попросим шаблон отыскать в строке то место, которое начинается на пробел.

Если в строке не будет пробела в начале, то мы не получим ничего для замены.

Если строка будет начинаться с пробела, то мы получим номер позиции пробела для его замены.

Давайте это запишем в виде шаблона RegExp:

/^ /

А теперь запишем это полностью в виде общего выражения с методом и какой-нибудь строкой:

" BLABLA".replace(/^ /,"")

Обратите внимание! Мы специально установили один пробел в начале строки.

Удалили один пробел в начале строки методом replace и RegExp - JavaScript
Удалили один пробел в начале строки методом replace и RegExp - JavaScript

Мы удалили всего один пробел в начале строки. Но этого недостаточно. А что если в начале строки будет на 1 пробел а 21? А если 7? Как быть в такой ситуации? Нам же нужно иметь универсальное решение, которое не зависит от количества начальных пробелов строки.

 

Шаг 3 - Квантификаторы

Нам нужно дополнить наше "Утверждение" ещё одним понятием синтаксиса шаблона регулярного выражения, которое называется "Квантификатор". Всего существует 12 производств квантификаторов. Для решения нашей задачи мы можем воспользоваться квантификатором, который обозначается символом плюса +.

Как он работает? Слева от него нужно установить искомый символ для сопоставления строки. Квантификатор + попытается отыскать самую длинную последовательность из повторяющихся символов, который установлен слева от +.

Если мы после пробела установим квантификатор +, то мы сможем отловить все вариации повторений пробелов от 1 до бесконечности раз.

Наложив это на наше "Утверждение начала строки", мы получаем шаблон RegExp вида:

/^ +/

Теперь давайте применим его на реальных примерах:

"  BLA2".replace(/^ +/,"")
'BLA2'
"   BLA3".replace(/^ +/,"")
'BLA3'
"        BLA8".replace(/^ +/,"")
'BLA8'
Универсальный шаблон удаления пробелов в начале строки - JavaScript
Универсальный шаблон удаления пробелов в начале строки - JavaScript

Мы научились удалять любое количество пробелов в начале строки при помощи правильного шаблона регулярного выражения

Шаг 4 - Альтернативы

Теперь было бы неплохо повторить обработку пробелов в конце строки. И желательно сделать это совместно с отловом пробелов из начала строки. Как это сделать?

В шаблонах регулярных выражений есть ещё одно понятие, которое называется "Альтернатива". Оно обозначается вертикальной линией |.

Дизъюнкция (Disjunction), Альтернатива (Alternative), Терм (Term) в RegExp - JavaScript
Дизъюнкция (Disjunction), Альтернатива (Alternative), Терм (Term) в RegExp - JavaScript

Альтернатива работает по принципу оператора ИЛИ. Если не ТО, то ЭТО. Воспринимайте Альтернативу как Ветвление в алгоритмах. (только на уровне регулярного выражения).

 

...

За конец строки отвечает Утверждение, которое обозначается символом доллара - $. Этот символ ждёт, что слева от него будут какие то символы, на которые должна оканчиваться строка.

Если мы слева от символа $ поставим один пробел, то тем самым мы попросим шаблон отыскать в строке то место, которое заканчивается на пробел.

Если в строке не будет пробела в конце, то мы не получим ничего для замены.

Если строка будет заканчиваться пробелом, то мы получим номер позиции пробела для его замены.

Если мы к этому добавим квантификатор +, то получим универсальное решение, удаляющее все пробелы в конце строки.

"Утверждение конца строки" шаблона RegExp будет выглядеть так:

/ +$/

Два Утверждения с одной Альтернативой:

/^ +| +$/

А теперь запишем это полностью в виде общего выражения с методом и какой-нибудь строкой:

"     HAHAH         ".replace(/^ +| +$/,"")

Мы получаем интересный результат:

'HAHAH         '
Сработала только одна Альтернатива в RegExp - JavaScript
Сработала только одна Альтернатива в RegExp - JavaScript

Почему пробелы на конце строки никуда не исчезни ведь мы правильно использовали синтаксис двух Альтернатив? Почему пробелы не удалились в конце строки?

Шаг 5 - Глобальный флаг регулярного выражения

По умолчанию все регулярные выражения работают таким образом, что при нахождении самого первого сопоставления в строке слева-направо, они прекращают свою работу и не просматривают оставшуюся часть строки.

Напомню, что Альтернатива - это Ветвление. То есть при сопоставлении нашей строки слева-направо, первыми встречаются пробелы НАЧАЛА СТРОКИ. Они вычисляются в максимальной своей длине. После этого регулярное выражение завершает свою работу т. к. одно из условий Ветвления выполнилось. Второе условие просто игнорируется и не выполняется. Как быть?

Чтобы сопоставлять строку до самого конца (без остановок), нужно использовать глобальный флаг регулярного выражения, который обозначается символом g.

Глобальный флаг устанавливается после правой границы регулярного выражения (после второй косой линии /). Выглядит это так:

//g

Применим это знание с нашим предыдущим кодом:

"     HAHAH         ".replace(/^ +| +$/g,"")

И мы получаем строку без пробелов в начале и в конце.

Глобальный флаг помог выполнить две Альтернативы в RegExp - JavaScript
Глобальный флаг помог выполнить две Альтернативы в RegExp - JavaScript

Пробелы в начале и в конце строки удалены. Ветвление выполнилось два раза из-за глобального флага.

Шаг 6 - Обработка пробелов из середины строки

Мы хотим обработать середину строки вместе с остальными Альтернативами, которые у нас уже есть. Что это значит?

Это означает, что мы хотим НАЙДЕННОЕ в СЕРЕДИНЕ также заменять на НИЧЕГО, как это было с НАЧАЛОМ и КОНЦОМ.

По логике выходит то, что мы должны отлавливать все места в середине строки, где количество пробелов два и более. И вот тут можно попасть в копкан, если воспользоваться квантификатором фигурных скобок {}. Например в таком выражении:

/ {2,}/g

Этот способ будет идеально работать, если количество пробелов будет нечётным. Но как только нам встретится чётное количество пробелов в строке, то ЗАМЕНА на НИЧЕГО просто склеит два слова вместе, не оставив ни единого пробела. Это ошибка!

...

Этот шаг требует ещё одного понимания процессов формирования шаблонов регулярных выражений. Следующая концепция называется "Атом со спецификатором группы". Звучит страшновато, но по факту всё просто.

Группа оформляется при помощи круглых скобок внутри шаблона регулярного выражения. Внутри самой группы, нам нужно прописывать наш искомый пробел. За пределами группы (справа), тоже должен быть пробел с привычным нам квантификатором +.

Выглядит это так:

/( ) +/g

В результате мы всегда пытаемся отыскать как минимум два подряд символа пробела. То есть любые одинарные пробелы 100% не попадут под это сопоставление.

Первый пробел в нашем случае изолирован группой. Все остальные пробелы отслеживаются квантификатором. То есть наш квантификатор как бы не видит первый пробел в шаблоне(не влияет на него).

Шаблон мы написали, но теперь нужно вернуться ко второму параметру метода replace().

Мы ждём ситуацию, когда сработает третья альтернатива из шаблона:

/^ +| +$|( ) +/g

Чтобы вытащить единственный пробел из захваченной круглыми скобками группы, нам нужно указать в качестве заменяемого значения знак доллара и номера группы. Выглядит это так "$1". Это обычная строка, но "под капотом" у метода replace() есть чёткая инструкция как нужно обрабатывать подобные строки. Алгоритм обработки подробно описан в стандарте ECMAScript в разделе абстрактной операции GetSubstitution().

Вернём к самой первой строке:

let stroka = "   aa bb  cc   dd    ee     ff     "

Произведём необходимую замену:

3 альтернативы в RegExp удалили лишние пробелы в строке - JavaScript
3 альтернативы в RegExp удалили лишние пробелы в строке - JavaScript

 

Дополнительная информация

Последовательность Альтернатив важна. Для работы по удалению лишних пробелов первой должна быть Альтернатива НАЧАЛА, потом КОНЦА и только потом СЕРЕДИНЫ.

Информационные ссылки

JavaScript - RegExp - Квантификатор "Фигурные скобки"

JavaScript - RegExp - Утверждения

Стандарт ECMAScript - Раздел "22.1.3.18 String.prototype.replace ( searchValue, replaceValue )" - https://tc39.es/ecma262/multipage/text-processing.html#sec-string.prototype.replace