使用Java正则表达式匹配字母字符,且不带百分号

时间:2019-05-16 19:56:44

标签: java regex string

tl; dr:

我想要一个类似ab%cde%fg hij %klm n%op

的字符串

并将其转换为以下任意一种(均可接受):

  • 'ab'%c'de'%f'g hij '%k'lm n'%o'p'
  • 'ab'%c'de'%f'g' 'hij' %k'lm' 'n'%o'p'
  • 'a''b'%c'd''e'%f'g' 'h''i''j' %k'l''m' 'n'%o'p'

(如果字母字符前面没有%,则必须将其放在单引号内。可以使用开合将多余的单引号引起来)

用例

我正在尝试在C strftime format中输入一个字符串,并将其转换为可与Java的SimpleDateFormat一起使用。在大多数情况下,这很简单:

String format = "%y-%m-%d %H:%M:%S";

Map<String, String> replacements = new HashMap<String, String>() {{
    put("%a", "EEE");
    put("%A", "EEEE");
    put("%b", "MMM");
    put("%B", "MMMM");
    put("%c", "EEE MMM dd HH:mm:ss yyyy");
    // ... for each strftime token, create a mapping ...
}};

for ( String key : replacements.keySet() )
{
    // apply the mappings one at a time
    format = format.replaceAll(key, replacements.get(key));
}

// Then format
SimpleDateFormat df = new SimpleDateFormat(format, Locale.getDefault());
System.out.println(df.format(Calendar.getInstance().getTime()));

但是,当我介绍字符文字时,就会遇到问题。根据{{​​1}}文档,不带百分号的 all 字符文字将被传递而不修改输出字符串。所以:

strftime

但是对于Format: "%y is a great year!" Output: "2019 is a great year!" ,除非所有字符文字都被单引号引起来,否则它们将被视为标记:

SimpleDateFormat

所需的输出

由于Format: "yyyy 'is a great year!'" Output: "2019 is a great year!" Format: "yyyy is a great year!" Output: ERROR - invalid token "i" 令牌始终是 单个字符 ,因此,修复格式字符串并不难。在最坏的情况下,“如果字母前没有strftime符号,请用单引号引起来”,这将导致:

%

这很丑陋,但是会达到预期的效果,并且是可以接受的答案。理想情况下,我们会包装 所有不带Format: "%y is a great year!" Processed: "%y 'i''s' 'a' 'g''r''e''a''t' 'y''e''a''r'!" 的字母字符,如下所示:

%

或者更好的是,所有运行 ,包括非字母和非Format: "%y is a great year!" Processed: "%y 'is' 'a' 'great' 'year'!" 字符

%

我尝试过的

我从一个漫不经心的正则表达式开始,我很确定那是行不通的,而且没有:

Format: "%y is a great year!"
Processed: "%y' is a great year!'"

我对后向引用没有足够的了解,所以我给了他们些许回旋,但同时也弄乱了一些东西:

format.replaceAll("[^%]([a-zA-Z]+)", "'$1'");
// Format:   "Literal %t Literal"
// Output:   "'iteral' %t'Literal'"
// Expected: "'Literal' %t 'Literal'"

我还考虑编写一个 非常 简单的词法分析器。像这样:

format.replaceAll("(?!%)([a-zA-Z]+)", "'$1'");
// Format:   "Literal %t Literal"
// Output:   "'Literal' %'t' 'Literal'"
// Expected: "'Literal' %t 'Literal'"

但是,我了解到StringBuffer s = new StringBuffer(); boolean inQuote = false; for (int i = 0; i < format.length; i++) { if (format[i] == '%') { i++; s.append(replacements.get(format[i]); } else if (inQuote) { s.append(format[i]); } else { s.append("'"); inQuote = true; s.append(format[i]); } } 不是有效的Java语法,并且在我决定只在此处发表文章之前,并没有花太多时间研究如何从字符串中正确获取字符。

我希望使用正则表达式解决方案,以便可以将其写在一行中,而不是像这样的循环。

2 个答案:

答案 0 :(得分:1)

已更新为与单个正则表达式一起使用。可以添加其他格式来测试正确性。

      String[] formats = { "ab%cde%fg hij %klm n%op", "ab%c", "%d"
      };
      for (String f : formats) {
         String parsed = f.replaceAll("(^[a-z]+|(?<=%[a-z])([a-z ]+))", "'$1'");
         System.out.println(parsed);
      }

两种可能性是:

  • [a-z]+之后的所有字符%[a-z]放在单引号之间。
  • 放置%之前且以上之间不包括的所有字符 单引号。

答案 1 :(得分:1)

既然已经考虑过,为什么不使用几个replaceAll函数。

  

首先,在所有连续的字符串中添加单引号;

     

然后,将单引号后跟%的位置移动一个字符;

     

最后,删除空引号。

下面是我在Python中的测试代码。我相信它也可以在Java等其他语言中使用。

>>> str1=re.sub("([a-zA-Z]+)","'\g<1>'",input)
>>> str2=re.sub("%'([a-zA-Z])'","%\g<1>",str1)
>>> str3=re.sub("''","",str2)
>>> str1
"'Literal' %'t' 'Literal'"
>>> str2
"'Literal' %t 'Literal'"
>>> str3
"'Literal' %t 'Literal'"