Regex для замены всех \n в строке, но не внутри тега [code] [/code]
Мне нужна помощь, чтобы заменить все символы \n (новая строка) для
в строке, но не те \ n внутри тегов [code][/code]. Мой мозг горит, я не могу решить это самостоятельно:(
Пример:
test test test
test test test
test
test
[code]some
test
code
[/code]
more text
Должно быть:
test test test<br />
test test test<br />
test<br />
test<br />
<br />
[code]some
test
code
[/code]<br />
<br />
more text<br />
Спасибо за ваше время. С наилучшими пожеланиями.
6 ответов
Я бы предложил (простой) парсер, а не регулярное выражение. Примерно так (плохой псевдокод):
stack elementStack;
foreach(char in string) {
if(string-from-char == "[code]") {
elementStack.push("code");
string-from-char = "";
}
if(string-from-char == "[/code]") {
elementStack.popTo("code");
string-from-char = "";
}
if(char == "\n" && !elementStack.contains("code")) {
char = "<br/>\n";
}
}
Вы пометили вопрос регулярным выражением, но это, возможно, не лучший инструмент для работы.
Возможно, вам лучше использовать базовые методы компиляции (т. Е. Лексер, питающий простой анализатор конечного автомата).
Ваш лексер идентифицирует пять токенов: ("[code]", '\n', "[/code]", EOF,: все остальные строки:) и ваш конечный автомат выглядит следующим образом:
действие токена штата ------------------------ начало: нет: -> вне out [code] OUTPUT(токен), -> в out \n OUTPUT(перерыв), OUTPUT(токен) out * OUTPUT(токен) в [/code] OUTPUT(токен), -> out в * ВЫХОД (токен) * EOF -> конец
РЕДАКТИРОВАТЬ: я вижу другой плакат, обсуждающий возможную необходимость вложения блоков. Этот конечный автомат не справится с этим. Для вложения блоков используйте рекурсивный приличный синтаксический анализатор (не такой простой, но все же достаточно легкий и расширяемый).
РЕДАКТИРОВАТЬ: Axeman отмечает, что этот дизайн исключает использование "/ код" в коде. Механизм побега может быть использован, чтобы победить это. Что-то вроде добавления '\' к вашим токенам и добавление:
действие токена штата ------------------------ в \ -> вход esc-in * OUTPUT(токен), -> в out \ -> выход esc-out * OUTPUT(токен), -> out
к государственной машине.
Применяются обычные аргументы в пользу машинно-генерируемых лексеров и парсеров.
Это, кажется, делает это:
private final static String PATTERN = "\\*+";
public static void main(String args[]) {
Pattern p = Pattern.compile("(.*?)(\\[/?code\\])", Pattern.DOTALL);
String s = "test 1 ** [code]test 2**blah[/code] test3 ** blah [code] test * 4 [code] test 5 * [/code] * test 6[/code] asdf **";
Matcher m = p.matcher(s);
StringBuffer sb = new StringBuffer(); // note: it has to be a StringBuffer not a StringBuilder because of the Pattern API
int codeDepth = 0;
while (m.find()) {
if (codeDepth == 0) {
m.appendReplacement(sb, m.group(1).replaceAll(PATTERN, ""));
} else {
m.appendReplacement(sb, m.group(1));
}
if (m.group(2).equals("[code]")) {
codeDepth++;
} else {
codeDepth--;
}
sb.append(m.group(2));
}
if (codeDepth == 0) {
StringBuffer sb2 = new StringBuffer();
m.appendTail(sb2);
sb.append(sb2.toString().replaceAll(PATTERN, ""));
} else {
m.appendTail(sb);
}
System.out.printf("Original: %s%n", s);
System.out.printf("Processed: %s%n", sb);
}
Это не простое регулярное выражение, но я не думаю, что вы можете делать то, что хотите, с простым регулярным выражением. Не с обработкой вложенных элементов и пр.
Как упоминалось другими авторами, регулярные выражения - не лучший инструмент для работы, потому что они почти повсеместно реализованы как жадные алгоритмы. Это означает, что даже если вы попытались сопоставить блоки кода, используя что-то вроде:
(\[code\].*\[/code\])
Тогда выражение будет соответствовать всему с первого [code]
пометить до последнего [/code]
тег, который явно не то, что вы хотите. Хотя есть способы обойти это, получающиеся регулярные выражения обычно хрупки, неинтуитивны и совершенно безобразны. Нечто подобное следующему коду Python будет работать намного лучше.
output = []
def add_brs(str):
return str.replace('\n','<br/>\n')
# the first block will *not* have a matching [/code] tag
blocks = input.split('[code]')
output.push(add_brs(blocks[0]))
# for all the rest of the blocks, only add <br/> tags to
# the segment after the [/code] segment
for block in blocks[1:]:
if len(block.split('[/code]'))!=1:
raise ParseException('Too many or few [/code] tags')
else:
# the segment in the code block is pre, everything
# after is post
pre, post = block.split('[/code]')
output.push(pre)
output.push(add_brs(post))
# finally join all the processed segments together
output = "".join(output)
Обратите внимание, что приведенный выше код не был протестирован, это всего лишь приблизительное представление о том, что вам нужно сделать.
Это трудно, потому что если регулярные выражения хороши в поиске чего-либо, они не так хороши в сопоставлении всего, кроме чего-то... Так что вы должны использовать цикл, я сомневаюсь, что вы можете сделать это за один раз.
После поиска я нашел что-то близкое к решению cletus, за исключением того, что предположил, что блок кода не может быть вложенным, что приводит к более простому коду: выберите то, что соответствует вашим потребностям.
import java.util.regex.*;
class Test
{
static final String testString = "foo\nbar\n[code]\nprint'';\nprint{'c'};\n[/code]\nbar\nfoo";
static final String replaceString = "<br>\n";
public static void main(String args[])
{
Pattern p = Pattern.compile("(.+?)(\\[code\\].*?\\[/code\\])?", Pattern.DOTALL);
Matcher m = p.matcher(testString);
StringBuilder result = new StringBuilder();
while (m.find())
{
result.append(m.group(1).replaceAll("\\n", replaceString));
if (m.group(2) != null)
{
result.append(m.group(2));
}
}
System.out.println(result.toString());
}
}
Быстрый быстрый тест, вам нужно больше (ноль, пустая строка, без тега кода, несколько и т. Д.).
Чтобы сделать это правильно, вам действительно нужно сделать три прохода:
- Найдите блоки [code] и замените их уникальным токеном + индексом (сохраняя исходный блок), например, "foo [code]abc[/code] bar[code]efg[/code]" становится "foo TOKEN-1 barTOKEN-2"
- Сделайте замену новой строки.
- Сканирование на наличие аварийных токенов и восстановление исходного блока.
Код выглядит примерно так:
Matcher m = escapePattern.matcher(input);
while(m.find()) {
String key = nextKey();
escaped.put(key,m.group());
m.appendReplacement(output1,"TOKEN-"+key);
}
m.appendTail(output1);
Matcher m2 = newlinePatten.matcher(output1);
while(m2.find()) {
m.appendReplacement(output2,newlineReplacement);
}
m2.appendTail(output2);
Matcher m3 = Pattern.compile("TOKEN-(\\d+)").matcher(output2);
while(m3.find()) {
m.appendReplacement(finalOutput,escaped.get(m3.group(1)));
}
m.appendTail(finalOutput);
Это быстрый и грязный способ. Существуют более эффективные способы (другие упоминали парсер / лексеры), но если вы не обрабатываете миллионы строк и ваш код не привязан к процессору (а не к вводу / выводу, как большинство веб-приложений), и вы подтвердили с помощью профилировщика, что это узкое место, они, вероятно, не стоят этого.
* Я не запускал его, это все из памяти. Просто проверьте API, и вы сможете решить его.