Regex для замены всех \ n в строке, но не внутри тега [code] [/ code] - PullRequest
9 голосов
/ 30 ноября 2008

Мне нужна помощь для замены всех символов \ 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 ]

7 голосов
/ 30 ноября 2008

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

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";
    }
}
6 голосов
/ 30 ноября 2008

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

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

Ваш лексер определил бы пять токенов: ("[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

РЕДАКТИРОВАТЬ: я вижу другой плакат, обсуждающий возможную необходимость вложения блоков. Этот конечный автомат не справится с этим. Для вложения блоков используйте рекурсивный приличный синтаксический анализатор (не такой простой, но все же достаточно легкий и расширяемый).

РЕДАКТИРОВАТЬ: Axeman отмечает, что этот дизайн исключает использование "[/ code]" в коде. Механизм побега может быть использован, чтобы победить это. Что-то вроде добавления '\' к вашим токенам и добавление:

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

к конечному автомату.

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

3 голосов
/ 30 ноября 2008

Это, кажется, делает это:

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

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

2 голосов
/ 30 ноября 2008

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

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

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

1 голос
/ 30 ноября 2008

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

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

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

1 голос
/ 30 ноября 2008

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

  1. Найдите блоки [code] и замените их уникальным токеном + индексом (сохраняя оригинальный блок), например, "foo [code] abc [/ code] bar [code] efg [/ code]" становится "foo TOKEN -1 барTOKEN-2 "
  2. Сделайте замену новой строки.
  3. Сканирование на наличие escape-токенов и восстановление исходного блока.

Код выглядит примерно так:

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 , и вы сможете разобраться с этим.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...