有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

将字母字符与前面没有百分号的Java正则表达式匹配

tl;博士:

我想要一个字符串,比如: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 ^{} 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()));

然而,当我引入字符文本时,它遇到了一些问题。根据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'!"

或者,更好的是,所有运行,包括非alpha和非%字符

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'"

我还考虑写一个非常简单的lexer。比如:

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

然而,我了解到format[i]不是有效的Java语法,在我决定在这里发布之前,我没有花太多时间研究如何正确地从字符串中获取字符

我更喜欢正则表达式解决方案,这样我就可以在一行中编写它,而不是像这样的循环


共 (2) 个答案

  1. # 1 楼答案

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

    First, add single quotes to all consecutive character strings;

    Then, move the single quote preceded by % by one character;

    Last, remove empty quotes.

    下面是我用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'"
    
  2. # 2 楼答案

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

          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]+放在单引号之间
    • %前面放置任何未包含在上面的字符 单引号