用正则表达式替换字符串中的所有 \n,但不包括 [code] [/code] 标签内的 \n。

9
我需要帮助将字符串中的所有 \n(换行)字符替换为
,但不包括 [code] [/code] 标签内的 \n。
例如:
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
我对这个看似简单的问题所引发的深入讨论感到有些惊讶。点赞。 - dmckee --- ex-moderator kitten
在.NET正则表达式中,这非常容易...太糟糕了,它是Java :( - Timothy Khouri
我告诉你,这绝不是简单的事情 :) - Tute
6个回答

7
我建议使用一个(简单的)解析器,而不是正则表达式。类似这样的东西(糟糕的伪代码):
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
您已经标记了问题regex,但这可能不是最好的工具。您可能更好地使用基本的编译器构建技术(即一个词法分析器feeding一个简单的状态机解析器)。
您的词法分析器将识别五个tokens:(“[code]”,'\n',“[/code]”,EOF,:所有其他字符串:),您的状态机如下:
状态 token 动作 ------------------------ begin :none: --> out out [code] 输出(token), --> in out \n 输出(break),输出(token) out * 输出(token) in [/code] 输出(token),--> out in * 输出(token) * EOF --> end
编辑:我看到其他发帖者讨论可能需要嵌套块。这个状态机不能处理它。对于嵌套块,请使用递归下降分析器(不太简单,但仍然足够简单和可扩展)。
编辑:Axeman注意到这个设计排除了在代码中使用“[/code]”。可以使用逃逸机制来避免这种情况。类似于将'\'添加到您的tokens并添加:
状态 token 动作 ------------------------ in \ -->esc-in esc-in * 输出(token),-->in out \ -->esc-out esc-out * 输出(token),-->out
到状态机。
使用生成的词法分析器和解析器是有益的原因。

这还不错,但它不允许代码使用字符串“[/code]”,或在注释中包含此值。然而,我们中的一些人也习惯在JavaScript中编写'</' + 'script>'。尽管如此,它仍不能让代码只是代码。 - Axeman
足够正确。但是OP没有为代码块识别出逃逸机制。"当我们第一次练习语言设计时,我们编织的网是多么纠结啊。"或者类似的话。 - dmckee --- ex-moderator kitten

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

这不是一个简单的正则表达式,但我认为你不能用简单的正则表达式来实现你想要的功能。因为它无法处理嵌套元素等情况。


不错,但我怀疑[code]标签可以嵌套(至少在标准BBCode中)。 - PhiLho
1
真正的问题是该算法可以被修改以正确处理任意嵌套。 - cletus

2

正如其他帖子所述,正则表达式并不是最好的工具,因为它们几乎普遍实现为贪婪算法。这意味着即使您尝试使用以下内容匹配代码块:

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

请注意,上面的代码并未经过测试,只是一个大致的想法,您需要做的事情。

对于这种情况,显然不会有嵌套的[代码]块,因此勉强量词就可以解决这个问题。例如,"\ [code] .*? \ [/ code]"一旦遇到“[/ code]”就会停止。 - noah
你的正则表达式描述有一点错误(正如@noah所指出的),但Python看起来不错(至少在理论上)。 - strager
这不是解决问题的坏方法,但如果问题变得更加复杂,它不会轻易地推广。无论如何,还是要点赞。 - dmckee --- ex-moderator kitten

1
为了做到正确,你需要进行三次处理:
  1. 查找 [code] 块并将其替换为唯一的标记+索引(保存原始块),例如,“foo [code]abc[/code] bar[code]efg[/code]” 变成 "foo TOKEN-1 barTOKEN-2"
  2. 进行换行符替换。
  3. 扫描转义标记并恢复原始块。

代码大致如下:

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

这是一种快速而不太规范的方法。还有更有效率的方法(其他人已经提到了解析器/词法分析器),但除非你正在处理数百万行代码且你的代码受CPU限制(而不是像大多数Web应用程序那样受I/O限制),并且你已经通过分析器确认这是瓶颈,否则它们可能不值得。

* 我没有运行它,这都是从记忆中得出的。只需检查API,您就能理解它。


你在写解析器方面所说的成本是正确的,但它们也随着问题陈述的复杂度而扩展得很好。而且这可能会比这个问题更大。 - dmckee --- ex-moderator kitten

1

这很困难,因为如果正则表达式擅长查找某些内容,那么它们就不擅长匹配除了某些内容之外的所有内容...所以你必须使用循环,我怀疑你无法一次性完成。

搜索后,我发现了与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());
  }
}

这只是一个简单的测试,你需要更多的测试(null、空字符串、没有代码标签、多个等等)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接