У меня есть гигантская строка (уценка), которая содержит что-то вроде этого:
## Заголовок 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 количество абзацы).
Как 3.0 до или после заголовка? Он окружен текстом, как и 2.0. Или вы имеете в виду в предложении перед заголовком?
@JerryJeremiah верно, я должен быть более конкретным, мне нужно захватить маркер, только если он находится после или до шаблона ##<followed by any character including spaces>
Но {~3.0} находится в середине текста? Я имею в виду, что каждый {~x.x} находится «после или перед ##<за которым следует любой символ, включая пробелы>»?
@ThomasFrank да, это уценка, но не понимаю вопроса, имеет ли значение, работаю ли я над бэкэндом или над интерфейсом? Я просто получаю эту уценку в виде строки, и мне нужно захватить определенные ее части.
Потому что, если это уценка, преобразование ее в html может упростить... и включение библиотеки для этого может немного отличаться на бэкэнде и на внешнем интерфейсе. Если 3.0 является ошибкой в вашем массиве, то, если он был преобразован в html, вам нужно будет искать только числа, которые являются первыми и последними в теге абзаца...
Это будет работать (но имеет ограничения...) (?|{([^}]*(~\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, потому что он далеко не ##
@JerryJeremiah: :) Начинает выглядеть так, как будто я много писал на Perl в те дни. Хорошо сделано... но, как и со всеми сложными регулярными выражениями, их немного сложно поддерживать :)
@ThomasFrank да, каждый маркер {~x.x} всегда находится в середине текста
@JerryJeremiah Я почти уверен, что этот вопрос имеет тег javascript, а вариант ECMAScript не поддерживает синтаксис группы сброса ветвей.
@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
@JerryJeremiah, это работает с текущим примером текстов «заголовков» ## Header <1-9>, но в моем приложении я не знаю, какой текст будет после ##, я должен был указать это в OP, я обновлю его. И я только что понял, что маркер 9.0 не находится ни после, ни перед каким-либо «заголовком», но мне он тоже нужен, я тоже включу эту часть в свое обновление.
Я чувствую себя действительно глупо - но глядя на пример, я все еще не понимаю, почему в массиве 3.0, а не 2.0, или 9.0, но не 8.0
@GhostOrder Вот что я имел в виду под (but has limitations...) Вы не можете заставить нефиксированную ширину смотреть назад ....
@ThomasFrank Я предположил, что ему нужен маркер в предложении после или перед ## Вот почему мое регулярное выражение ищет маркер, а затем некоторые вещи, за которыми следует . Конечно, он также хочет {9.0}, который и близко не стоит ## Итак Я думаю, это означает, что он хочет поставить отметки в первом и последнем предложениях каждого заголовка. И я не уверен, как это сделать.
@ThomasFrank нет, я плохой, я обновляю вопрос
Хорошо, забудьте о ## Что, если мы просто назовем маркеры в первом и последнем предложении каждого абзаца: (?<=\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}(?=[^.]*.\n|[^.]*.$)regex101.com/r/M3Lhym/3 Или раздел с разделителями ## может иметь более одного абзаца?
@JerryJeremiah «Вы не можете смотреть назад с нефиксированной шириной»: нет, в отличие от PCRE, ES поддерживает это.
Ах, теперь я понимаю первый и последний в каждом абзаце, в основном... (Если только в реальной строке нет заголовков уровня 3... конечно. Так что нет, @JerryJeremiah, я не думаю, что вы можете сделать такое предположение.. , Я бы разобрал его в HTML, поместил в «фальшивый DOM», например Cheerio, и получил его оттуда, хотя я понимаю, что регулярное выражение было бы элегантным...
ИЛИ: Как насчет того, чтобы сначала разбить на \n##, а затем зациклить каждую строку и проверить первое и последнее число в ней?
Хорошо, а что насчет этого. Я думаю, что он делает все, что вы хотите: (?<=##[^\n]+\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}(?=[^.]*\.\n\n##[^\n]+\n|[^.]*\.$)regex101.com/r/M3Lhym/4
Хорошо, я только что обновил вопрос, надеюсь, теперь он лучше объяснен.
Это мой последний - я думаю, он делает то, что вы хотите: (?<=##[^\n]+\n\n){([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}|{([^}]*(?:~\d*(?:\.\d+)?)[^}]*)}(?=[^.]*\.\n\n##[^\n]+\n|[^.]*\.\n*$)regex101.com/r/M3Lhym/6
Вы изменили ввод, но больше не даете ожидаемого результата. Пожалуйста, отредактируйте свой вопрос, иначе как мы можем перепроверить наши ответы?
Еще одна идея: str.split(/^\s*##[^#].*/m).forEach(v => { if (m = v.trim().match(/^{~[\ d.]+}|{~[\d.]+}$/g)) console.info(m); }); (демонстрация JS на tio.run)



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Это возможно с обходами, которые поддерживает 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, ''));
}
Гигантская уценка строки? Похоже на то... Фронтенд или бэкенд?