Regex заменить все \n на String, но не те внутри тега [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 />

Спасибо за ваше время. С наилучшими пожеланиями.

Ответ 1

Я бы предложил (простой) парсер, а не регулярное выражение. Что-то вроде этого (плохой псевдокод):

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";
    }
}

Ответ 2

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

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

Ваш лексер будет идентифицировать пять токенов: ( "[code]", "\n", "[code]", EOF,: все остальные строки:) и ваш конечный автомат выглядит так:

state    token    action
------------------------
begin    :none:   --> out
out      [code]   OUTPUT(token), --> in
out      \n       OUTPUT(break), OUTPUT(token)
out      *        OUTPUT(token)
in       [/code]  OUTPUT(token), --> out
in       *        OUTPUT(token)
*        EOF      --> end

EDIT: Я вижу другой плакат, обсуждая возможную необходимость вложения блоков. Этот конечный автомат не справится с этим. Для вложенных блоков используйте рекурсивный достойный парсер (не совсем простой, но все же достаточно простой и расширяемый).

EDIT: Axeman отмечает, что этот проект исключает использование "[/code]" в коде. Для этого можно использовать механизм эвакуации. Что-то вроде добавить '\' к вашим жетонам и добавить:

state    token    action
------------------------
in       \        -->esc-in
esc-in   *        OUTPUT(token), -->in
out      \        -->esc-out
esc-out  *        OUTPUT(token), -->out

для конечного автомата.

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

Ответ 3

Это похоже на это:

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);
}

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

Ответ 4

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

(\[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)

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

Ответ 5

Чтобы все было правильно, вам действительно нужно сделать три прохода:

  • Найдите [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);

Это быстрый и грязный способ. Есть более эффективные способы (другие упоминали парсер/лексеры), но если вы не обрабатываете миллионы строк, а ваш код связан с ЦП (а не привязан к I/O, как и большинство веб-приложений), и вы подтвердили с помощью профилировщика, что это узкое место, они, вероятно, не стоят того.

* Я не запускал его, это все из памяти. Просто проверьте API, и вы сможете это обработать.

Ответ 6

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

После поиска я нашел что-то близко к решению 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());
  }
}

Быстрый анализ теста, вам нужно больше (нуль, пустая строка, не тег кода, несколько и т.д.).