Регулярное выражение - соответствует шаблону, только если он находится после или до определенного шаблона

У меня есть гигантская строка (уценка), которая содержит что-то вроде этого:

## Заголовок 1

{~1.0} Lorem ipsum dolor sit amet. Сед конге диам turpis, {~2,0} vitae congueerat accumsan nec. {~3.0}

{~4.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~ 5,0} vitae congue erat accumsan nec. {~6.0}

{~7.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~8.0} vitae congue erat accumsan nec. {~9.0}

## Заголовок 2

{~10.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~11,0} vitae congue erat accumsan nec. {~12.0}

{~113.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~14,0} vitae congue erat accumsan nec. {~15,0}

{~16.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~17,0} vitae congue erat accumsan nec. {~18.0}

## Заголовок 3

{~19.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~20,0} vitae congue erat accumsan nec. {~21.0}

{~22.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~23,0} vitae congue erat accumsan nec. {~24,0}

{~25.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~26,0} vitae congue erat accumsan nec. {~27,0}

Это маркер {~x.x}

А «разделом» я буду называть сочетание заголовка и еще одного абзаца.

Мне нужно сопоставить первый и последний маркер каждого раздела.

В настоящее время я использую это регулярное выражение /\s?{([^}]*(~\d*(?:\.\d+)?)[^}]*)}\s?/g в javascript, которое я получил из выбранного ответа на этот вопрос, чтобы захватить все маркеры, но теперь мне нужно изменить его, чтобы захватить только первый и последний из каждого «раздела». .

Строка исходит из пользовательского ввода, поэтому я не могу заранее знать, сколько абзацев в «разделе» будет иметь ни содержимое заголовков, все, что я знаю, это то, что будет по крайней мере один раздел (то есть один заголовок, за которым следует x количество абзацы).

Гигантская уценка строки? Похоже на то... Фронтенд или бэкенд?

Thomas Frank 02.05.2023 01:26

Как 3.0 до или после заголовка? Он окружен текстом, как и 2.0. Или вы имеете в виду в предложении перед заголовком?

Jerry Jeremiah 02.05.2023 01:27

@JerryJeremiah верно, я должен быть более конкретным, мне нужно захватить маркер, только если он находится после или до шаблона ##<followed by any character including spaces>

GhostOrder 02.05.2023 01:30

Но {~3.0} находится в середине текста? Я имею в виду, что каждый {~x.x} находится «после или перед ##<за которым следует любой символ, включая пробелы>»?

Thomas Frank 02.05.2023 01:31

@ThomasFrank да, это уценка, но не понимаю вопроса, имеет ли значение, работаю ли я над бэкэндом или над интерфейсом? Я просто получаю эту уценку в виде строки, и мне нужно захватить определенные ее части.

GhostOrder 02.05.2023 01:31

Потому что, если это уценка, преобразование ее в html может упростить... и включение библиотеки для этого может немного отличаться на бэкэнде и на внешнем интерфейсе. Если 3.0 является ошибкой в ​​​​вашем массиве, то, если он был преобразован в html, вам нужно будет искать только числа, которые являются первыми и последними в теге абзаца...

Thomas Frank 02.05.2023 01:33

Это будет работать (но имеет ограничения...) (?|{([^}]*(~\d*(?:\.\d+)?)[^}]*)}(?=[^.]*.\n\n##)|(?<=## Header [0-9]\n\n){([^}]*(~\d*(?:\.\d+)?)[^}]*)}|(?<=## Header [0-9][0-9]\n\n){([^}]*(~\d*(?:\.\d+)?)[^}]*)}) Если это сработает для вас, я могу написать ответ. regex101.com/r/M3Lhym/1 Он не набирает 9.0, потому что он далеко не ##

Jerry Jeremiah 02.05.2023 01:35

@JerryJeremiah: :) Начинает выглядеть так, как будто я много писал на Perl в те дни. Хорошо сделано... но, как и со всеми сложными регулярными выражениями, их немного сложно поддерживать :)

Thomas Frank 02.05.2023 01:36

@ThomasFrank да, каждый маркер {~x.x} всегда находится в середине текста

GhostOrder 02.05.2023 01:38

@JerryJeremiah Я почти уверен, что этот вопрос имеет тег javascript, а вариант ECMAScript не поддерживает синтаксис группы сброса ветвей.

InSync 02.05.2023 01:39

@InSync Извините, я выбрал не ту вещь в Regex101 ... Это устраняет это, но подсовпадения находятся в разных группах {([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}(?=[^.]*.\n\n##)|(?<=## Header [0-9]\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|(?<=## Header [0-9][0-9]\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}regex101.com/r/M3Lhym/2

Jerry Jeremiah 02.05.2023 01:41

@JerryJeremiah, это работает с текущим примером текстов «заголовков» ## Header <1-9>, но в моем приложении я не знаю, какой текст будет после ##, я должен был указать это в OP, я обновлю его. И я только что понял, что маркер 9.0 не находится ни после, ни перед каким-либо «заголовком», но мне он тоже нужен, я тоже включу эту часть в свое обновление.

GhostOrder 02.05.2023 01:44

Я чувствую себя действительно глупо - но глядя на пример, я все еще не понимаю, почему в массиве 3.0, а не 2.0, или 9.0, но не 8.0

Thomas Frank 02.05.2023 01:44

@GhostOrder Вот что я имел в виду под (but has limitations...) Вы не можете заставить нефиксированную ширину смотреть назад ....

Jerry Jeremiah 02.05.2023 01:45

@ThomasFrank Я предположил, что ему нужен маркер в предложении после или перед ## Вот почему мое регулярное выражение ищет маркер, а затем некоторые вещи, за которыми следует . Конечно, он также хочет {9.0}, который и близко не стоит ## Итак Я думаю, это означает, что он хочет поставить отметки в первом и последнем предложениях каждого заголовка. И я не уверен, как это сделать.

Jerry Jeremiah 02.05.2023 01:47

@ThomasFrank нет, я плохой, я обновляю вопрос

GhostOrder 02.05.2023 01:49

Хорошо, забудьте о ## Что, если мы просто назовем маркеры в первом и последнем предложении каждого абзаца: (?<=\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:~\d*(?:\.\‌​d+)?)[^}]*)}(?=[^.]*‌​.\n|[^.]*.$)regex101.com/r/M3Lhym/3 Или раздел с разделителями ## может иметь более одного абзаца?

Jerry Jeremiah 02.05.2023 01:51

@JerryJeremiah «Вы не можете смотреть назад с нефиксированной шириной»: нет, в отличие от PCRE, ES поддерживает это.

InSync 02.05.2023 01:52

Ах, теперь я понимаю первый и последний в каждом абзаце, в основном... (Если только в реальной строке нет заголовков уровня 3... конечно. Так что нет, @JerryJeremiah, я не думаю, что вы можете сделать такое предположение.. , Я бы разобрал его в HTML, поместил в «фальшивый DOM», например Cheerio, и получил его оттуда, хотя я понимаю, что регулярное выражение было бы элегантным...

Thomas Frank 02.05.2023 01:53

ИЛИ: Как насчет того, чтобы сначала разбить на \n##, а затем зациклить каждую строку и проверить первое и последнее число в ней?

Thomas Frank 02.05.2023 01:55

Хорошо, а что насчет этого. Я думаю, что он делает все, что вы хотите: (?<=##[^\n]+\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:‌​~\d*(?:\.\d+)?)[^}]*‌​)}(?=[^.]*\.\n\n##[^‌​\n]+\n|[^.]*\.$)regex101.com/r/M3Lhym/4

Jerry Jeremiah 02.05.2023 01:57

Хорошо, я только что обновил вопрос, надеюсь, теперь он лучше объяснен.

GhostOrder 02.05.2023 02:10

Это мой последний - я думаю, он делает то, что вы хотите: (?<=##[^\n]+\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:‌​~\d*(?:\.\d+)?)[^}]*‌​)}(?=[^.]*\.\n\n##[^‌​\n]+\n|[^.]*\.\n*$)regex101.com/r/M3Lhym/6

Jerry Jeremiah 02.05.2023 02:10

Вы изменили ввод, но больше не даете ожидаемого результата. Пожалуйста, отредактируйте свой вопрос, иначе как мы можем перепроверить наши ответы?

Nick 02.05.2023 03:16

Еще одна идея: str.split(/^\s*##[^#].*/m).forEach(v => { if (m = v.trim().match(/^{~[\ d.]+}|{~[\d.]+}$/g)) console.info(m); }); (демонстрация JS на tio.run)

bobble bubble 02.05.2023 14:34
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
25
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это возможно с обходами, которые поддерживает JS.

Поскольку мы часто повторно используем исходный шаблон, давайте сохраним его в переменной:

const pattern = String.raw`{([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}`;

Строка, не содержащая приведенный выше шаблон, выглядит следующим образом, где [^] обозначает «все символы», подобно . с флагом s:

`(?:(?!${pattern})[^])*`

Исходя из этого, мы строим наш взгляд вперед и назад:

// Pattern, anything that doesn't contain pattern, then header or end of string (not end of line).
const lookahead = `${pattern}(?=(?:(?!${pattern})[^])*(?:^##.+|(?![^])))`;

// Header, anything that doesn't contain pattern, then pattern itself.
const lookbehind = `(?<=^##.+$(?:(?!${pattern})[^])*)${pattern}`;

Вот как проходят наши последние шаги:

const regex = new RegExp(`${lookbehind}|${lookahead}`, 'gm');

// Filter out unmatched groups.
[...text.matchAll(regex)].map(match => match.filter(Boolean));

Попробуй это:

console.config({ maximize: true });

function match(string) {
  const pattern = String.raw`{([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}`;
  const lookahead = `${pattern}(?=(?:(?!${pattern})[^])*(?:^##.+|(?![^])))`;
  const lookbehind = `(?<=^##.+$(?:(?!${pattern})[^])*)${pattern}`;
  const regex = new RegExp(`${lookbehind}|${lookahead}`, 'gm');
  
  console.info(regex); // Just to show you how monstrous it is.
  
  return string.matchAll(regex);
}

const text = `
## Header 1

{~1.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~2.0} vitae congue erat accumsan nec. {~3.0}

{~4.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~5.0} vitae congue erat accumsan nec. {~6.0}

{~7.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~8.0} vitae congue erat accumsan nec. {~9.0}

## Header 2

{~10.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~11.0} vitae congue erat accumsan nec. {~12.0}

{~113.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~14.0} vitae congue erat accumsan nec. {~15.0}

{~16.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~17.0} vitae congue erat accumsan nec. {~18.0}

## Header 3

{~19.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~20.0} vitae congue erat accumsan nec. {~21.0}

{~22.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~23.0} vitae congue erat accumsan nec. {~24.0}

{~25.0} Lorem ipsum dolor sit amet. Sed congue diam turpis, {~26.0} vitae congue erat accumsan nec. {~27.0}
`.trim();

console.info([...match(text)].map(match => match.filter(Boolean)));
<script src = "https://gh-canon.github.io/stack-snippet-console/console.min.js"></script>
Ответ принят как подходящий

Это мой вариант, меньше regexp:y, чем в большинстве других, но он работает:

function getNumbers(str) {
  return `\n${str}`.split('\n## ')
    .map(x => [...x.matchAll(/\{~(\d|\.)*\}/g)].map(x => x[0]))
    .map(x => [x[0], x.slice(-1)]).flat(2).filter(x => x)
    .map(x => +x.replace(/[\{\}~]/g, ''));
}

Другие вопросы по теме