Я пытаюсь удалить пустые строки между другими строками, которые соответствуют определенному шаблону. В моем случае этот шаблон - просто строка начинается с символа -.

const orig = `
- line1

- line2

- line3

- line4

- line5
`.trim();

const actual =
  orig.replace(/((?:^|\n)-.*\n)\n(-)/g, '$1$2');

В приведенном выше коде я использую регулярное выражение для соответствия:

  • перевод строки (или начало строки) с последующим ...
  • - строка с префиксом, за которой следует ..
  • пустая строка, за которой следует ...
  • еще один -

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

Где я ожидал, что приведенный выше код даст мне это:

- line1
- line2
- line3
- line4
- line5

... это на самом деле дает мне это:

- line1
- line2

- line3
- line4

- line5

Вот скрипка, которая демонстрирует проблему.

Вопрос. Как насчет регулярного выражения, вызывающего такое поведение?

Бонус: есть ли лучший способ сделать это? (например, через split / reduce - хотя я все еще хотел бы знать, почему это не работает)

1
FtDRbwLXw6 29 Авг 2017 в 20:32

6 ответов

Лучший ответ

Причина такого поведения в том, что регулярное выражение не перекрывает совпадения. Это потребляет и соответствует:

- line 1

- 

Заменяет на:

- line 1
- 

И затем продолжает проходить строку с конца предыдущего матча.

По этой причине он не соответствует следующей новой строке, потому что

  line 2

- line 3

Не содержит совпадения с вашим шаблоном. Следующее совпадение с вашим шаблоном будет

<newline>
- line 3

-

Заменяется:

<newline>
- line 3
-

Чтобы решить эту проблему, используйте lookaheads или lookbehinds, которые позволяют условное сопоставление на основе окружающих шаблонов без использования этих шаблонов .

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

const actual = orig.replace(/^(-.*\n)\n(?=-)/gm, '$1');

https://regex101.com/r/fPUkYh/4

Я также изменил ((?:^|\n)-.*\n)\n на ^(-.*\n)\n и добавил флаг m, потому что начало утверждения строки ^ не обязательно должно находиться в группе захвата, а \n приводит к удалению предыдущих новых строк.

Этот шаблон также может быть изменен для соответствия произвольному количеству строк bl; ank между строками, соответствующими шаблону:

/^(-.*\n)\n+(?=-)/gm

https://regex101.com/r/X7B7pi/2

2
Will Barnwell 29 Авг 2017 в 19:57

Вы можете сделать это следующим образом

const orig = `
- line1

- line2

- line3

- line4

- line5
`.trim();

const actual =
	orig.replace(/(\-[^\n]*)([^-]*)(?=-)/g, '$1\n');

document.getElementById('orig').innerText = orig;
document.getElementById('actual').innerText = actual;
<ul>
  <li><h3>Original</h3><pre id="orig"></pre></li>
  <li><h3>Expected</h3><pre>- line1<br />- line2<br />- line3<br />- line4<br />- line5</pre></li>
  <li><h3>Actual</h3><pre id="actual"></pre></li>
</ul>

См. демонстрационную версию regex

0
marvel308 29 Авг 2017 в 17:55

Последний - является частью шаблона потребления. После совпадения (-) после этого - устанавливается индекс регулярного выражения, и вы не можете найти это совпадение, поскольку - в (?:^|\n)- не может соответствовать этому -. Вы должны поместить это в положительную перспективу. Затем вам нужно использовать модификатор m, чтобы ^ соответствовал началу позиции line , а не только началу строки.

Использование

/((?:^|\n)-.*\n)\n(?=-)/gm

См. демонстрационную версию regex. Строка замены сокращена до $1, поскольку осталась только одна группа захвата.

Вот демо с фиксированным выражением:

const orig = `
- line1

- line2

- line3

- line4

- line5
`.trim();

const actual =
	orig.replace(/((?:^|\n)-.*\n)\n(?=-)/gm, '$1');

document.getElementById('orig').innerText = orig;
document.getElementById('actual').innerText = actual;
ul { font-family: sans-serif; list-style: none; padding: 0; }
li { display: inline-block; padding: 1em; vertical-align: top; }
<ul>
  <li><h3>Original</h3><pre id="orig"></pre></li>
  <li><h3>Expected</h3><pre>- line1<br />- line2<br />- line3<br />- line4<br />- line5</pre></li>
  <li><h3>Actual</h3><pre id="actual"></pre></li>
</ul>
2
Wiktor Stribiżew 29 Авг 2017 в 18:16

Достаточно просто при использовании модификатора Многострочный //m

 (                             # (1 start), Stuff to write back
      ^                             # BOL
      - .* 
      \r? \n      
 )                             # (1 end)
 \s*                           # Blank lines to remove
 \r? \n 
var orig_str = "- line1\n\n\n- line2\n\n- line3\n\n- line4\n\n- line5\n- line6";

var new_str =
	orig_str.replace(/(^-.*\r?\n)\s*\r?\n/mg, '$1');
  
  
console.log( "Original\n--------\n" +  orig_str + "\n" );
console.log( "New\n--------\n" +  new_str );

Выход

Original
--------
- line1


- line2

- line3

- line4

- line5
- line6


New
--------
- line1
- line2
- line3
- line4
- line5
- line6

Если между -lines есть то, что вам нужно, просто добавьте утверждение в
end (^ -. * \ r? \ n) \ s * \ r? \ n (? = - )

1
29 Авг 2017 в 18:35

Это даст вам то, что вам нужно -

const actual = orig.replace(/\n\n|\r\r/g, "\n");
-1
koolkat 29 Авг 2017 в 17:42

Вот более короткое регулярное выражение, включающее ваш паттерн в мах:

const actual = orig.replace(/(-.*\n)\n/g, '$1');

0
Kristianmitk 26 Апр 2018 в 23:41