【问题标题】:Regex to Retrieve Quoted String and Quote Character正则表达式检索引用的字符串和引用字符
【发布时间】:2015-12-22 23:17:55
【问题描述】:

我有一种语言将字符串定义为由单引号或双引号分隔,其中分隔符通过加倍在字符串中进行转义。例如,以下所有字符串都是合法字符串:

'This isn''t easy to parse.'
'Then John said, "Hello Tim!"'
"This isn't easy to parse."
"Then John said, ""Hello Tim!"""

我有一个字符串集合(上面定义),由不包含引号的东西分隔。我试图使用正则表达式做的是解析列表中的每个字符串。例如,这是一个输入:

"Some String #1" OR 'Some String #2' AND "Some 'String' #3" XOR
'一些“字符串”#4'你好“一些”“字符串”“#5”FOO'一些''字符串''#6'

判断一个字符串是否是这种形式的正则表达式很简单:

^(?:"(?:[^"]|"")*"|'(?:[^']|'')*')(?:\s+[^"'\s]+\s+(?:"(?:[^"]|"")*"|'(?:[^']|'')*')*

在运行上面的表达式来测试它是否是这种形式之后,我需要另一个正则表达式来从输入中获取每个分隔字符串。我打算这样做:

Pattern pattern = Pattern.compile("What REGEX goes here?");
Matcher matcher = pattern.matcher(inputString);
int startIndex = 0;
while (matcher.find(startIndex))
{
    String quote        = matcher.group(1);
    String quotedString = matcher.group(2);
    ...
    startIndex = matcher.end();
}

我想要一个正则表达式,它可以捕获第 1 组中的引号字符和第 2 组中引号内的文本(我使用的是 Java 正则表达式)。因此,对于上述输入,我正在寻找一个在每次循环迭代中产生以下输出的正则表达式:

Loop 1: matcher.group(1) = "
        matcher.group(2) = Some String #1
Loop 2: matcher.group(1) = '
        matcher.group(2) = Some String #2
Loop 3: matcher.group(1) = "
        matcher.group(2) = Some 'String' #3
Loop 4: matcher.group(1) = '
        matcher.group(2) = Some "String" #4
Loop 5: matcher.group(1) = "
        matcher.group(2) = Some ""String"" #5
Loop 6: matcher.group(1) = '
        matcher.group(2) = Some ''String'' #6

到目前为止我尝试过的模式(未转义,然后是 Java 代码的转义):

(["'])((?:[^\1]|\1\1)*)\1
"([\"'])((?:[^\\1]|\\1\\1)*)\\1"

(?<quot>")(?<val>(?:[^"]|"")*)"|(?<quot>')(?<val>(?:[^']|'')*)'
"(?<quot>\")(?<val>(?:[^\"]|\"\")*)\"|(?<quot>')(?<val>(?:[^']|'')*)'"

在尝试编译模式时,这两个都失败了。

这样的正则表达式可能吗?

【问题讨论】:

  • See the Javadoc。他们没有提到支持反斜杠引用:(
  • 在您链接的页面的大约四分之一处,有一个标题为“反向引用”的部分,其中包含文本“\n 无论第 n 个捕获组匹配”。
  • 您想在第二组中捕获的“引号之间的文本”是什么?是this isn''t easy to parse(转义)还是this isn't easy to parse未转义?
  • 绝对有必要使用正则表达式来解决这个问题,还是可以编写一些代码来解析字符串?
  • 澄清一下,您要捕获哪个引用:包含整个输入(第一个和最后一个字符)的引用,还是包含内部引用部分的引用?

标签: java regex


【解决方案1】:

创建一个适合您的实用程序类:

class test {
    private static Pattern pd = Pattern.compile("(\")((?:[^\"]|\"\")*)\"");
    private static Pattern ps = Pattern.compile("(')((?:[^']|'')*)'");
    public static Matcher match(String s) {
        Matcher md = pd.matcher(s);
        if (md.matches()) return md;
        else return ps.matcher(s);
    }
}

【讨论】:

  • 是的,这将返回一个字符串是否与给定的模式匹配,但如果双引号,结果将在第 1 组中,而单引号字符串将在第 2 组中。有没有办法获取第 1 组中的引号字符和第 2 组中的引号文本,而不管使用的引号类型如何?
  • 使用匹配左侧正则表达式的方法,然后是右侧。如果第一个匹配返回第一个匹配器,否则返回第二个匹配器
  • 是的,这就是我最终要做的。我意识到我没有业务规则说明我需要请求组中的匹配项,最终我只需要将双引号分隔的字符串转换为单引号分隔的字符串。我最终使用了正则表达式 "('(?:[^']|'')*')|\"((?:[^\"]|\"\")*)\""。如果组 # 1 匹配,我完成了。如果组 #2 匹配,我会做一些额外的工作来取消转义双引号和转义单引号。
【解决方案2】:

我不确定这是否是您要求的,但您可以编写一些代码来解析字符串并获得所需的结果(引号字符和内部文本),而不是使用正则表达式。

class Parser {

  public static ParseResult parse(String str)
  throws ParseException {

    if(str == null || (str.length() < 2)){
      throw new ParseException();
    }

    Character delimiter = getDelimiter(str);

    // Remove delimiters
    str = str.substring(1, str.length() -1);

    // Unescape escaped quotes in inner string
    String escapedDelim = "" + delimiter + delimiter;
    str = str.replaceAll(escapedDelim, "" + delimiter);

    return new ParseResult(delimiter, str);
  }

  private static Character getDelimiter(String str)
  throws ParseException {
    Character firstChar = str.charAt(0);
    Character lastChar = str.charAt(str.length() -1);

    if(!firstChar.equals(lastChar)){
      throw new ParseException(String.format(
            "First char (%s) doesn't match last char (%s) for string %s",
           firstChar, lastChar, str
      ));
    }

    return firstChar;
  }

}
class ParseResult {

  public final Character delimiter;
  public final String contents;

  public ParseResult(Character delimiter, String contents){
    this.delimiter = delimiter;
    this.contents = contents;
  }

}
class ParseException extends Exception {

  public ParseException(){
    super();
  }

  public ParseException(String msg){
    super(msg);
  }

}

【讨论】:

    【解决方案3】:

    使用这个正则表达式:

    "^('|\")(.*)\\1$"
    

    一些测试代码:

    public static void main(String[] args) {
        String[] tests = {
                "'This isn''t easy to parse.'",
                "'Then John said, \"Hello Tim!\"'",
                "\"This isn't easy to parse.\"",
                "\"Then John said, \"\"Hello Tim!\"\"\""};
        Pattern pattern = Pattern.compile("^('|\")(.*)\\1$");
        Arrays.stream(tests).map(pattern::matcher).filter(Matcher::find).forEach(m -> System.out.println("1=" + m.group(1) + ", 2=" + m.group(2)));
    }
    

    输出:

    1=', 2=这不容易解析。 1=', 2=然后约翰说:“你好,蒂姆!” 1=", 2=这不容易解析。 1=", 2=然后约翰说,""你好,蒂姆!""

    如果您对如何在文本中捕获引用的文本感兴趣:

    此正则表达式匹配所有变体并捕获第 1 组中的引用和第 6 组中的引用文本:

    ^((')|("))(.*?("\3|")(.*)\5)?.*\1$
    

    live demo


    这是一些测试代码:

    public static void main(String[] args) {
        String[] tests = {
                "'This isn''t easy to parse.'",
                "'Then John said, \"Hello Tim!\"'",
                "\"This isn't easy to parse.\"",
                "\"Then John said, \"\"Hello Tim!\"\"\""};
        Pattern pattern = Pattern.compile("^((')|(\"))(.*?(\"\\3|\")(.*)\\5)?.*\\1$");
        Arrays.stream(tests).map(pattern::matcher).filter(Matcher::find)
          .forEach(m -> System.out.println("quote=" + m.group(1) + ", quoted=" + m.group(6)));
    }
    

    输出:

    报价=',报价=空 quote=',quoted=你好蒂姆! 引用=“,引用=空 quote=",quoted=你好蒂姆!

    【讨论】:

    • 我意识到我的问题不清楚,并试图消除导致您得到这个答案的歧义。
    • @Jeff Easy。查看新答案。
    • 好的,我的测试用例显然没有涵盖我想要做的事情,即从逗号分隔的列表中解析引号分隔的字符串。我将添加另一个测试来澄清。
    【解决方案4】:

    对这类问题使用正则表达式非常具有挑战性。不使用正则表达式的简单解析器更容易实现、理解和维护。

    此外,这种简单的解析可以轻松支持反斜杠转义,以及将反斜杠序列转换为字符(例如“\n”转换为换行符)。

    【讨论】:

      【解决方案5】:

      这可以通过如下所示的简单正则表达式轻松完成

      private static Object[] checkPattern(String name, String regex) {
          List<String> matchedString = new ArrayList<>();
          Pattern pattern = Pattern.compile(regex);
          Matcher matcher = pattern.matcher(name);
          while (matcher.find()) {
              if (matcher.group().length() > 0) {
                  matchedString.add(matcher.group());
              }
          }
          return matchedString.toArray();
      }
      
      
      @Test
      public void quotedtextMultipleQuotedLines() {
          String text = "He said, \"I am Tom\". She said, \"I am Lisa\".";
          String quoteRegex = "(\"[^\"]+\")";
          String[] strArray = {"\"I am Tom\"", "\"I am Lisa\""};
          assertArrayEquals(strArray, checkPattern(text, quoteRegex));
      }
      

      我们在这里得到字符串作为数组元素。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-05
        • 2017-05-13
        • 1970-01-01
        • 2011-01-27
        • 1970-01-01
        • 1970-01-01
        • 2013-11-08
        • 2019-12-28
        相关资源
        最近更新 更多